Skip to main content

rusteal_core/
console.rs

1use rusteal_ffi::{RustealConsoleArgs, UObjectHandle};
2
3use crate::error::{RustealError, RustealResult, check_ffi_ctx};
4use crate::{delegate_registry, ffi_dispatch};
5
6pub fn register_command(
7    name: &str,
8    help: &str,
9    mut run: impl FnMut(&[&str], UObjectHandle) + Send + 'static,
10) -> RustealResult<()> {
11    let callback_id = delegate_registry::register_callback(move |params| {
12        let args = unsafe { &*(params as *const RustealConsoleArgs) };
13
14        let text = if args.args.is_null() {
15            ""
16        } else {
17            let bytes = unsafe { std::slice::from_raw_parts(args.args, args.args_len as usize) };
18
19            std::str::from_utf8(bytes).unwrap_or("")
20        };
21
22        let words: Vec<&str> = if text.is_empty() {
23            Vec::new()
24        } else {
25            text.split('\n').collect()
26        };
27
28        run(&words, args.world);
29    });
30
31    let code = unsafe {
32        ffi_dispatch::console_register_command(
33            name.as_ptr(),
34            name.len() as u32,
35            help.as_ptr(),
36            help.len() as u32,
37            callback_id,
38        )
39    };
40
41    let result = check_ffi_ctx(code, name);
42
43    if result.is_err() {
44        delegate_registry::unregister_callback(callback_id);
45    }
46
47    result
48}
49
50pub trait ConsoleValue: Sized {
51    #[doc(hidden)]
52    const KIND: u32;
53    fn to_text(&self) -> String;
54    fn from_text(text: &str) -> Option<Self>;
55}
56
57impl ConsoleValue for bool {
58    const KIND: u32 = 0;
59    fn to_text(&self) -> String {
60        if *self { "1" } else { "0" }.to_string()
61    }
62    fn from_text(text: &str) -> Option<Self> {
63        match text.trim().to_ascii_lowercase().as_str() {
64            "1" | "true" | "yes" | "on" => Some(true),
65            "0" | "false" | "no" | "off" | "" => Some(false),
66            other => other.parse::<f64>().ok().map(|n| n != 0.0),
67        }
68    }
69}
70
71impl ConsoleValue for i32 {
72    const KIND: u32 = 1;
73    fn to_text(&self) -> String {
74        self.to_string()
75    }
76    fn from_text(text: &str) -> Option<Self> {
77        let text = text.trim();
78
79        text.parse()
80            .ok()
81            .or_else(|| text.parse::<f64>().ok().map(|n| n as i32))
82    }
83}
84
85impl ConsoleValue for f32 {
86    const KIND: u32 = 2;
87    fn to_text(&self) -> String {
88        self.to_string()
89    }
90    fn from_text(text: &str) -> Option<Self> {
91        text.trim().parse().ok()
92    }
93}
94
95impl ConsoleValue for String {
96    const KIND: u32 = 3;
97    fn to_text(&self) -> String {
98        self.clone()
99    }
100    fn from_text(text: &str) -> Option<Self> {
101        Some(text.to_string())
102    }
103}
104
105#[derive(Clone, Copy, Debug, PartialEq, Eq)]
106pub struct ConsoleVariable<T> {
107    name: &'static str,
108    _value: std::marker::PhantomData<fn() -> T>,
109}
110
111impl<T: ConsoleValue> ConsoleVariable<T> {
112    pub const fn new(name: &'static str) -> Self {
113        Self {
114            name,
115            _value: std::marker::PhantomData,
116        }
117    }
118
119    pub fn register(&self, help: &str, default: T) -> RustealResult<()> {
120        let default = default.to_text();
121
122        let code = unsafe {
123            ffi_dispatch::console_register_variable(
124                self.name.as_ptr(),
125                self.name.len() as u32,
126                help.as_ptr(),
127                help.len() as u32,
128                T::KIND,
129                default.as_ptr(),
130                default.len() as u32,
131            )
132        };
133
134        check_ffi_ctx(code, self.name)
135    }
136
137    pub fn name(&self) -> &'static str {
138        self.name
139    }
140
141    pub fn get(&self) -> RustealResult<T> {
142        let text = get_variable_text(self.name)?;
143
144        T::from_text(&text).ok_or_else(|| {
145            RustealError::Internal(format!("console variable {} holds '{text}'", self.name))
146        })
147    }
148
149    pub fn set(&self, value: T) -> RustealResult<()> {
150        set_variable_text(self.name, &value.to_text())
151    }
152}
153
154pub fn unregister(name: &str) -> RustealResult<()> {
155    check_ffi_ctx(
156        unsafe { ffi_dispatch::console_unregister(name.as_ptr(), name.len() as u32) },
157        name,
158    )
159}
160
161pub fn get_variable_text(name: &str) -> RustealResult<String> {
162    let mut buf = vec![0u8; 256];
163
164    loop {
165        let mut len: u32 = 0;
166
167        let code = unsafe {
168            ffi_dispatch::console_get_variable(
169                name.as_ptr(),
170                name.len() as u32,
171                buf.as_mut_ptr(),
172                buf.len() as u32,
173                &mut len,
174            )
175        };
176
177        check_ffi_ctx(code, name)?;
178
179        if (len as usize) <= buf.len() {
180            buf.truncate(len as usize);
181
182            return String::from_utf8(buf)
183                .map_err(|_| RustealError::Internal("console value is not UTF-8".into()));
184        }
185
186        buf.resize(len as usize, 0);
187    }
188}
189
190pub fn set_variable_text(name: &str, value: &str) -> RustealResult<()> {
191    check_ffi_ctx(
192        unsafe {
193            ffi_dispatch::console_set_variable(
194                name.as_ptr(),
195                name.len() as u32,
196                value.as_ptr(),
197                value.len() as u32,
198            )
199        },
200        name,
201    )
202}
203
204#[cfg(test)]
205mod tests {
206    use super::*;
207
208    #[test]
209    fn values_read_the_console_text() {
210        assert_eq!(bool::from_text("1"), Some(true));
211        assert_eq!(bool::from_text("False"), Some(false));
212        assert_eq!(bool::from_text("2"), Some(true));
213        assert_eq!(i32::from_text("42"), Some(42));
214        assert_eq!(i32::from_text("3.0"), Some(3));
215        assert_eq!(f32::from_text("0.5"), Some(0.5));
216        assert_eq!(f32::from_text("x"), None);
217        assert_eq!(String::from_text("a b"), Some("a b".to_string()));
218        assert_eq!(true.to_text(), "1");
219        assert_eq!(7.to_text(), "7");
220    }
221}