From 224d7ef4be6b11a7fb187f8e6c2b5e279e5090c0 Mon Sep 17 00:00:00 2001 From: Christopher Serr Date: Wed, 1 Nov 2023 15:54:11 +0100 Subject: [PATCH] Support maps and strings as setting values (#75) This allows for more complex settings to be stored. There is no GUI equivalent for those yet though. --- src/runtime/settings/value.rs | 89 ++++++++++++++++++++++++++----- src/runtime/sys.rs | 98 ++++++++++++++++++++++++++--------- 2 files changed, 151 insertions(+), 36 deletions(-) diff --git a/src/runtime/settings/value.rs b/src/runtime/settings/value.rs index d80e66a..08ae05e 100644 --- a/src/runtime/settings/value.rs +++ b/src/runtime/settings/value.rs @@ -2,28 +2,41 @@ use core::mem::MaybeUninit; use crate::runtime::sys; +use super::Map; + /// A value of a setting. This can be a value of any type that a setting can -/// hold. Currently only boolean settings are supported. +/// hold. Currently booleans, strings and maps are supported. #[derive(Debug)] #[repr(transparent)] pub struct Value(pub(super) sys::SettingValue); -impl Drop for Value { - #[inline] - fn drop(&mut self) { - // SAFETY: The handle is valid and we own it, so it's our responsibility - // to free it. - unsafe { sys::setting_value_free(self.0) } - } -} - impl Value { - /// Creates a new setting value from a value of a supported type. + /// Creates a new setting value from a value of a supported type. The value + /// is going to be copied inside. Any changes to the original value are not + /// reflected in the setting value. #[inline] pub fn new(value: impl Into) -> Self { value.into() } + /// Returns the value as a map if it is a map. The map is a copy, so any + /// changes to it are not reflected in the setting value. + #[inline] + pub fn get_map(&self) -> Option { + // SAFETY: The handle is valid. We provide a valid pointer to a map. + // After the function call we check the return value and if it's true, + // the map is initialized and we can return it. We also own the map + // handle, so it's our responsibility to free it. + unsafe { + let mut out = MaybeUninit::uninit(); + if sys::setting_value_get_map(self.0, out.as_mut_ptr()) { + Some(Map(out.assume_init())) + } else { + None + } + } + } + /// Returns the value as a boolean if it is a boolean. #[inline] pub fn get_bool(&self) -> Option { @@ -39,12 +52,64 @@ impl Value { } } } + + /// Returns the value as a string if it is a string. + #[cfg(feature = "alloc")] + #[inline] + pub fn get_string(&self) -> Option { + // SAFETY: The handle is valid. We provide a null pointer and 0 as the + // length to get the length of the string. If it failed and the length + // is 0, then that indicates that the value is not a string and we + // return None. Otherwise we allocate a buffer of the returned length + // and call the function again with the buffer. This should now always + // succeed and we can return the string. The function also guarantees + // that the buffer is valid UTF-8. + unsafe { + let mut len = 0; + let sucess = sys::setting_value_get_string(self.0, core::ptr::null_mut(), &mut len); + if len == 0 && !sucess { + return None; + } + let mut buf = alloc::vec::Vec::with_capacity(len); + let success = sys::setting_value_get_string(self.0, buf.as_mut_ptr(), &mut len); + assert!(success); + buf.set_len(len); + Some(alloc::string::String::from_utf8_unchecked(buf)) + } + } +} + +impl Drop for Value { + #[inline] + fn drop(&mut self) { + // SAFETY: The handle is valid and we own it, so it's our responsibility + // to free it. + unsafe { sys::setting_value_free(self.0) } + } +} + +impl From<&Map> for Value { + #[inline] + fn from(value: &Map) -> Self { + // SAFETY: The handle is valid. We retain ownership of the handle, so we + // only take a reference to a map. We own the returned value now. + Self(unsafe { sys::setting_value_new_map(value.0) }) + } } impl From for Value { #[inline] fn from(value: bool) -> Self { - // SAFETY: This is always safe to call. + // SAFETY: This is always safe to call. We own the returned value now. Self(unsafe { sys::setting_value_new_bool(value) }) } } + +impl From<&str> for Value { + #[inline] + fn from(value: &str) -> Self { + // SAFETY: We provide a valid pointer and length to the string which is + // guaranteed to be valid UTF-8. We own the returned value now. + Self(unsafe { sys::setting_value_new_string(value.as_ptr(), value.len()) }) + } +} diff --git a/src/runtime/sys.rs b/src/runtime/sys.rs index 42be7e4..ff58469 100644 --- a/src/runtime/sys.rs +++ b/src/runtime/sys.rs @@ -56,8 +56,9 @@ extern "C" { pub fn timer_undo_split(); /// Resets the timer. pub fn timer_reset(); - /// Sets a custom key value pair. This may be arbitrary information that - /// the auto splitter wants to provide for visualization. + /// Sets a custom key value pair. This may be arbitrary information that the + /// auto splitter wants to provide for visualization. The pointers need to + /// point to valid UTF-8 encoded text with the respective given length. pub fn timer_set_variable( key_ptr: *const u8, key_len: usize, @@ -74,23 +75,25 @@ extern "C" { /// automatic flow of time for the game time. pub fn timer_resume_game_time(); - /// Attaches to a process based on its name. + /// Attaches to a process based on its name. The pointer needs to point to + /// valid UTF-8 encoded text with the given length. pub fn process_attach(name_ptr: *const u8, name_len: usize) -> Option; /// Attaches to a process based on its process id. pub fn process_attach_by_pid(pid: ProcessId) -> Option; /// Detaches from a process. pub fn process_detach(process: Process); - /// Lists processes based on their name. Returns `false` if listing the - /// processes failed. If it was successful, the buffer is now filled - /// with the process ids. They are in no specific order. The - /// `list_len_ptr` will be updated to the amount of process ids that - /// were found. If this is larger than the original value provided, the - /// buffer provided was too small and not all process ids could be - /// stored. This is still considered successful and can optionally be - /// treated as an error condition by the caller by checking if the - /// length increased and potentially reallocating a larger buffer. If - /// the length decreased after the call, the buffer was larger than - /// needed and the remaining entries are untouched. + /// Lists processes based on their name. The name pointer needs to point to + /// valid UTF-8 encoded text with the given length. Returns `false` if + /// listing the processes failed. If it was successful, the buffer is now + /// filled with the process ids. They are in no specific order. The + /// `list_len_ptr` will be updated to the amount of process ids that were + /// found. If this is larger than the original value provided, the buffer + /// provided was too small and not all process ids could be stored. This is + /// still considered successful and can optionally be treated as an error + /// condition by the caller by checking if the length increased and + /// potentially reallocating a larger buffer. If the length decreased after + /// the call, the buffer was larger than needed and the remaining entries + /// are untouched. pub fn process_list_by_name( name_ptr: *const u8, name_len: usize, @@ -108,23 +111,31 @@ extern "C" { buf_ptr: *mut u8, buf_len: usize, ) -> bool; - - /// Gets the address of a module in a process. + /// Gets the address of a module in a process. The pointer needs to point to + /// valid UTF-8 encoded text with the given length. pub fn process_get_module_address( process: Process, name_ptr: *const u8, name_len: usize, ) -> Option; - /// Gets the size of a module in a process. + /// Gets the size of a module in a process. The pointer needs to point to + /// valid UTF-8 encoded text with the given length. pub fn process_get_module_size( process: Process, name_ptr: *const u8, name_len: usize, ) -> Option; - + /// Stores the file system path of the executable in the buffer given. The + /// path is a path that is accessible through the WASI file system, so a + /// Windows path of `C:\foo\bar.exe` would be returned as + /// `/mnt/c/foo/bar.exe`. Returns `false` if the buffer is too small. After + /// this call, no matter whether it was successful or not, the `buf_len_ptr` + /// will be set to the required buffer size. If `false` is returned and the + /// `buf_len_ptr` got set to 0, the path does not exist or failed to get + /// read. The path is guaranteed to be valid UTF-8 and is not + /// nul-terminated. #[cfg(feature = "alloc")] pub fn process_get_path(process: Process, buf_ptr: *mut u8, buf_len_ptr: *mut usize) -> bool; - /// Gets the number of memory ranges in a given process. pub fn process_get_memory_range_count(process: Process) -> Option; /// Gets the start address of a memory range by its index. @@ -138,7 +149,8 @@ extern "C" { /// Sets the tick rate of the runtime. This influences the amount of /// times the `update` function is called per second. pub fn runtime_set_tick_rate(ticks_per_second: f64); - /// Prints a log message for debugging purposes. + /// Prints a log message for debugging purposes. The pointer needs to point + /// to valid UTF-8 encoded text with the given length. pub fn runtime_print_message(text_ptr: *const u8, text_len: usize); /// Stores the name of the operating system that the runtime is running /// on in the buffer given. Returns `false` if the buffer is too small. @@ -158,7 +170,8 @@ extern "C" { /// Adds a new boolean setting that the user can modify. This will return /// either the specified default value or the value that the user has set. /// The key is used to store the setting and needs to be unique across all - /// types of settings. + /// types of settings. The pointers need to point to valid UTF-8 encoded + /// text with the respective given length. pub fn user_settings_add_bool( key_ptr: *const u8, key_len: usize, @@ -169,7 +182,8 @@ extern "C" { /// Adds a new title to the user settings. This is used to group settings /// together. The heading level determines the size of the title. The top /// level titles use a heading level of 0. The key needs to be unique across - /// all types of settings. + /// all types of settings. The pointers need to point to valid UTF-8 encoded + /// text with the respective given length. pub fn user_settings_add_title( key_ptr: *const u8, key_len: usize, @@ -178,7 +192,8 @@ extern "C" { heading_level: u32, ); /// Adds a tooltip to a setting based on its key. A tooltip is useful for - /// explaining the purpose of a setting to the user. + /// explaining the purpose of a setting to the user. The pointers need to + /// point to valid UTF-8 encoded text with the respective given length. pub fn user_settings_set_tooltip( key_ptr: *const u8, key_len: usize, @@ -215,6 +230,8 @@ extern "C" { /// Inserts a copy of the setting value into the settings map based on the /// key. If the key already exists, it will be overwritten. You still retain /// ownership of the setting value, which means you still need to free it. + /// The pointer needs to point to valid UTF-8 encoded text with the given + /// length. pub fn settings_map_insert( map: SettingsMap, key_ptr: *const u8, @@ -224,22 +241,55 @@ extern "C" { /// Gets a copy of the setting value from the settings map based on the key. /// Returns `None` if the key does not exist. Any changes to it are only /// perceived if it's stored back. You own the setting value and are - /// responsible for freeing it. + /// responsible for freeing it. The pointer needs to point to valid UTF-8 + /// encoded text with the given length. pub fn settings_map_get( map: SettingsMap, key_ptr: *const u8, key_len: usize, ) -> Option; + /// Creates a new setting value from a settings map. The value is a copy of + /// the settings map. Any changes to the original settings map afterwards + /// are not going to be perceived by the setting value. You own the setting + /// value and are responsible for freeing it. You also retain ownership of + /// the settings map, which means you still need to free it. + pub fn setting_value_new_map(value: SettingsMap) -> SettingValue; /// Creates a new boolean setting value. You own the setting value and are /// responsible for freeing it. pub fn setting_value_new_bool(value: bool) -> SettingValue; + /// Creates a new string setting value. The pointer needs to point to valid + /// UTF-8 encoded text with the given length. You own the setting value and + /// are responsible for freeing it. + pub fn setting_value_new_string(value_ptr: *const u8, value_len: usize) -> SettingValue; /// Frees a setting value. pub fn setting_value_free(value: SettingValue); + /// Gets the value of a setting value as a settings map by storing it into + /// the pointer provided. Returns `false` if the setting value is not a + /// settings map. No value is stored into the pointer in that case. No + /// matter what happens, you still retain ownership of the setting value, + /// which means you still need to free it. You own the settings map and are + /// responsible for freeing it. + pub fn setting_value_get_map(value: SettingValue, value_ptr: *mut SettingsMap) -> bool; /// Gets the value of a boolean setting value by storing it into the pointer /// provided. Returns `false` if the setting value is not a boolean. No /// value is stored into the pointer in that case. No matter what happens, /// you still retain ownership of the setting value, which means you still /// need to free it. pub fn setting_value_get_bool(value: SettingValue, value_ptr: *mut bool) -> bool; + /// Gets the value of a string setting value by storing it into the buffer + /// provided. Returns `false` if the buffer is too small or if the setting + /// value is not a string. After this call, no matter whether it was + /// successful or not, the `buf_len_ptr` will be set to the required buffer + /// size. If `false` is returned and the `buf_len_ptr` got set to 0, the + /// setting value is not a string. The string is guaranteed to be valid + /// UTF-8 and is not nul-terminated. No matter what happens, you still + /// retain ownership of the setting value, which means you still need to + /// free it. + #[cfg(feature = "alloc")] + pub fn setting_value_get_string( + value: SettingValue, + buf_ptr: *mut u8, + buf_len_ptr: *mut usize, + ) -> bool; }