From 4e6468163f7359b94a270ff61ce93e44d48e4150 Mon Sep 17 00:00:00 2001 From: Christopher Serr Date: Sun, 12 Nov 2023 15:05:47 +0100 Subject: [PATCH] Add support for Settings Lists This adds support for `List`, `i64`, and `f64` values. It also extends the `Map` API to support iteration. All types also now implement `Clone` and `Debug`. --- src/emulator/gba/mednafen.rs | 11 +-- src/emulator/gba/mod.rs | 6 +- src/emulator/gba/vba.rs | 34 ++++--- src/runtime/process.rs | 1 - src/runtime/settings/list.rs | 109 ++++++++++++++++++++++ src/runtime/settings/map.rs | 169 +++++++++++++++++++++++++++++++++- src/runtime/settings/mod.rs | 2 + src/runtime/settings/value.rs | 135 +++++++++++++++++++++++++-- src/runtime/sys.rs | 84 +++++++++++++++++ 9 files changed, 514 insertions(+), 37 deletions(-) create mode 100644 src/runtime/settings/list.rs diff --git a/src/emulator/gba/mednafen.rs b/src/emulator/gba/mednafen.rs index d9e0707..6b9e4c3 100644 --- a/src/emulator/gba/mednafen.rs +++ b/src/emulator/gba/mednafen.rs @@ -33,9 +33,9 @@ impl State { addr }; - self.cached_iwram_pointer = { - const SIG2: Signature<13> = Signature::new("48 8B 05 ?? ?? ?? ?? 81 E1 FF 7F 00 00"); + const SIG2: Signature<13> = + Signature::new("48 8B 05 ?? ?? ?? ?? 81 E1 FF 7F 00 00"); let ptr: Address = SIG2.scan_process_range(game, main_module_range)? + 3; let mut addr: Address = ptr + 0x4 + game.read::(ptr).ok()?; @@ -45,8 +45,8 @@ impl State { return None; } } - - addr + + addr }; let ewram = game.read::(self.cached_ewram_pointer).ok()?; @@ -60,13 +60,12 @@ impl State { game.read::(ptr + 1).ok()?.into() }; - self.cached_iwram_pointer = { const SIG2: Signature<11> = Signature::new("A1 ?? ?? ?? ?? 81 ?? FF 7F 00 00"); let ptr = SIG2.scan_process_range(game, main_module_range)?; game.read::(ptr + 1).ok()?.into() }; - + let ewram = game.read::(self.cached_ewram_pointer).ok()?; let iwram = game.read::(self.cached_iwram_pointer).ok()?; diff --git a/src/emulator/gba/mod.rs b/src/emulator/gba/mod.rs index 8e97338..7a594b1 100644 --- a/src/emulator/gba/mod.rs +++ b/src/emulator/gba/mod.rs @@ -3,12 +3,12 @@ use crate::{Address, Error, Process}; use bytemuck::CheckedBitPattern; +mod emuhawk; +mod mednafen; mod mgba; mod nocashgba; mod retroarch; mod vba; -mod emuhawk; -mod mednafen; /// A Nintendo Gameboy Advance emulator that the auto splitter is attached to. pub struct Emulator { @@ -84,7 +84,7 @@ impl Emulator { false => { self.ram_base = None; false - }, + } } } diff --git a/src/emulator/gba/vba.rs b/src/emulator/gba/vba.rs index 6db3633..62f05e8 100644 --- a/src/emulator/gba/vba.rs +++ b/src/emulator/gba/vba.rs @@ -48,24 +48,24 @@ impl State { return None; } } - - addr - }; + addr + }; self.is_emulating = { - const SIG_RUNNING: Signature<19> = Signature::new("83 3D ?? ?? ?? ?? 00 74 ?? 80 3D ?? ?? ?? ?? 00 75 ?? 66"); - const SIG_RUNNING2: Signature<16> = Signature::new("48 8B 15 ?? ?? ?? ?? 31 C0 8B 12 85 D2 74 ?? 48"); + const SIG_RUNNING: Signature<19> = + Signature::new("83 3D ?? ?? ?? ?? 00 74 ?? 80 3D ?? ?? ?? ?? 00 75 ?? 66"); + const SIG_RUNNING2: Signature<16> = + Signature::new("48 8B 15 ?? ?? ?? ?? 31 C0 8B 12 85 D2 74 ?? 48"); if let Some(ptr) = SIG_RUNNING.scan_process_range(game, main_module_range) { let ptr = ptr + 2; - ptr + 0x4 + game.read::(ptr).ok()? + 0x1 + ptr + 0x4 + game.read::(ptr).ok()? + 0x1 } else { let ptr = SIG_RUNNING2.scan_process_range(game, main_module_range)? + 3; let ptr = ptr + 0x4 + game.read::(ptr).ok()?; game.read::(ptr).ok()?.into() } - }; let ewram = game.read::(self.cached_ewram_pointer).ok()?; @@ -75,7 +75,7 @@ impl State { } else { const SIG: Signature<11> = Signature::new("A1 ?? ?? ?? ?? 81 ?? FF FF 03 00"); const SIG_OLD: Signature<12> = Signature::new("81 E6 FF FF 03 00 8B 15 ?? ?? ?? ??"); - + if let Some(ptr) = SIG.scan_process_range(game, main_module_range) { self.cached_ewram_pointer = game.read::(ptr + 1).ok()?.into(); self.cached_iwram_pointer = { @@ -83,17 +83,20 @@ impl State { let ptr = SIG2.scan_process_range(game, main_module_range)?; game.read::(ptr + 1).ok()?.into() }; - + self.is_emulating = { - const SIG: Signature<19> = Signature::new("83 3D ?? ?? ?? ?? 00 74 ?? 80 3D ?? ?? ?? ?? 00 75 ?? 66"); - const SIG_OLD: Signature<13> = Signature::new("8B 15 ?? ?? ?? ?? 31 C0 85 D2 74 ?? 0F"); + const SIG: Signature<19> = + Signature::new("83 3D ?? ?? ?? ?? 00 74 ?? 80 3D ?? ?? ?? ?? 00 75 ?? 66"); + const SIG_OLD: Signature<13> = + Signature::new("8B 15 ?? ?? ?? ?? 31 C0 85 D2 74 ?? 0F"); - let ptr = SIG.scan_process_range(game, main_module_range) + let ptr = SIG + .scan_process_range(game, main_module_range) .or_else(|| SIG_OLD.scan_process_range(game, main_module_range))?; game.read::(ptr + 2).ok()?.into() }; - + let ewram = game.read::(self.cached_ewram_pointer).ok()?; let iwram = game.read::(self.cached_iwram_pointer).ok()?; @@ -104,11 +107,12 @@ impl State { self.cached_iwram_pointer = self.cached_ewram_pointer.add_signed(0x4); self.is_emulating = { - const SIG_RUNNING: Signature<11> = Signature::new("8B 0D ?? ?? ?? ?? 85 C9 74 ?? 8A"); + const SIG_RUNNING: Signature<11> = + Signature::new("8B 0D ?? ?? ?? ?? 85 C9 74 ?? 8A"); let ptr = SIG_RUNNING.scan_process_range(game, main_module_range)? + 2; game.read::(ptr).ok()?.into() }; - + let ewram = game.read::(self.cached_ewram_pointer).ok()?; let iwram = game.read::(self.cached_iwram_pointer).ok()?; diff --git a/src/runtime/process.rs b/src/runtime/process.rs index 1179b47..a323ca4 100644 --- a/src/runtime/process.rs +++ b/src/runtime/process.rs @@ -11,7 +11,6 @@ use super::{sys, Error, MemoryRange}; pub use super::sys::ProcessId; /// A process that the auto splitter is attached to. -#[derive(Debug)] #[repr(transparent)] pub struct Process(pub(super) sys::Process); diff --git a/src/runtime/settings/list.rs b/src/runtime/settings/list.rs new file mode 100644 index 0000000..4174d61 --- /dev/null +++ b/src/runtime/settings/list.rs @@ -0,0 +1,109 @@ +use core::fmt; + +use crate::{runtime::sys, Error}; + +use super::Value; + +/// A list of [`Value`]s that can itself be a [`Value`] and thus be stored in a +/// [`Map`](super::Map). +#[repr(transparent)] +pub struct List(pub(super) sys::SettingsList); + +impl fmt::Debug for List { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_list().entries(self.iter()).finish() + } +} + +impl Drop for List { + #[inline] + fn drop(&mut self) { + // SAFETY: The handle is valid and we own it, so it's our responsibility + // to free it. + unsafe { sys::settings_list_free(self.0) } + } +} + +impl Clone for List { + #[inline] + fn clone(&self) -> Self { + // SAFETY: The handle is valid, so we can safely copy it. + Self(unsafe { sys::settings_list_copy(self.0) }) + } +} + +impl Default for List { + #[inline] + fn default() -> Self { + Self::new() + } +} + +impl List { + /// Creates a new empty settings list. + #[inline] + pub fn new() -> Self { + // SAFETY: This is always safe to call. + Self(unsafe { sys::settings_list_new() }) + } + + /// Returns the number of values in the list. + #[inline] + pub fn len(&self) -> u64 { + // SAFETY: The handle is valid, so we can safely call this function. + unsafe { sys::settings_list_len(self.0) } + } + + /// Returns [`true`] if the list has a length of 0. + #[inline] + pub fn is_empty(&self) -> bool { + self.len() == 0 + } + + /// Returns a copy of the value at the given index. Returns [`None`] if the + /// index is out of bounds. Any changes to it are only perceived if it's + /// stored back. + #[inline] + pub fn get(&self, index: u64) -> Option { + // SAFETY: The settings list handle is valid. + unsafe { sys::settings_list_get(self.0, index).map(Value) } + } + + /// Pushes a copy of the value to the end of the list. + #[inline] + pub fn push(&self, value: &Value) { + // SAFETY: The settings list handle is valid and the value handle is + // valid. + unsafe { sys::settings_list_push(self.0, value.0) } + } + + /// Inserts a copy of the value at the given index, pushing all values at + /// and after the index one position further. Returns an error if the index + /// is out of bounds. You may specify an index that is equal to the length + /// of the list to append the value to the end of the list. + #[inline] + pub fn insert(&self, index: u64, value: &Value) -> Result<(), Error> { + // SAFETY: The settings list handle is valid and the value handle is + // valid. + unsafe { + if sys::settings_list_insert(self.0, index, value.0) { + Ok(()) + } else { + Err(Error {}) + } + } + } + + /// Returns an iterator over the values in the list. Every value is a copy, + /// so any changes to them are only perceived if they are stored back. The + /// iterator is double-ended, so it can be iterated backwards as well. While + /// it's possible to modify the list while iterating over it, it's not + /// recommended to do so, as the iterator might skip values or return + /// duplicate values. In that case it's better to clone the list before and + /// iterate over the clone or use [`get`](Self::get) to manually handle the + /// iteration. + #[inline] + pub fn iter(&self) -> impl DoubleEndedIterator + '_ { + (0..self.len()).flat_map(|i| self.get(i)) + } +} diff --git a/src/runtime/settings/map.rs b/src/runtime/settings/map.rs index 104191c..0164a46 100644 --- a/src/runtime/settings/map.rs +++ b/src/runtime/settings/map.rs @@ -1,4 +1,8 @@ -use crate::runtime::sys; +use core::fmt; + +use arrayvec::ArrayString; + +use crate::{runtime::sys, Error}; use super::Value; @@ -11,10 +15,19 @@ use super::Value; /// the settings widget is used as the key for the settings map. Additional /// settings that are not part of the GUI can be stored in the map as well, such /// as a version of the settings for handling old versions of an auto splitter. -#[derive(Debug)] #[repr(transparent)] pub struct Map(pub(super) sys::SettingsMap); +impl fmt::Debug for Map { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + #[cfg(feature = "alloc")] + let entries = self.iter(); + #[cfg(not(feature = "alloc"))] + let entries = self.iter_array_string::<128>(); + f.debug_map().entries(entries).finish() + } +} + impl Drop for Map { #[inline] fn drop(&mut self) { @@ -95,4 +108,156 @@ impl Map { // and length to the key which is guaranteed to be valid UTF-8. unsafe { sys::settings_map_get(self.0, key.as_ptr(), key.len()).map(Value) } } + + /// Returns the number of key value pairs in the map. + #[inline] + pub fn len(&self) -> u64 { + // SAFETY: The handle is valid, so we can safely call this function. + unsafe { sys::settings_map_len(self.0) } + } + + /// Returns [`true`] if the map has a length of 0. + #[inline] + pub fn is_empty(&self) -> bool { + self.len() == 0 + } + + /// Returns the key at the given index. Returns [`None`] if the index is out + /// of bounds. + #[cfg(feature = "alloc")] + #[inline] + pub fn get_key_by_index(&self, index: u64) -> 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 index is out of bounds 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 success = + sys::settings_map_get_key_by_index(self.0, index, core::ptr::null_mut(), &mut len); + if len == 0 && !success { + return None; + } + let mut buf = alloc::vec::Vec::with_capacity(len); + let success = + sys::settings_map_get_key_by_index(self.0, index, buf.as_mut_ptr(), &mut len); + assert!(success); + buf.set_len(len); + Some(alloc::string::String::from_utf8_unchecked(buf)) + } + } + + /// Returns the key at the given index as an [`ArrayString`]. Returns + /// [`None`] if the index is out of bounds. Returns an error if the key is + /// does not fit into the array string. + #[inline] + pub fn get_key_by_index_array_string( + &self, + index: u64, + ) -> Option, Error>> { + // SAFETY: The handle is valid. We provide a pointer to our buffer and + // the length of the buffer. If the function fails, we check the length + // and if it's 0, then that indicates that the index is out of bounds + // and we return None. Otherwise we return an error. If the function + // succeeds, we set the length of the buffer to the returned length and + // return the string. The function also guarantees that the buffer is + // valid UTF-8. + unsafe { + let mut buf = ArrayString::::new(); + let mut len = N; + let success = sys::settings_map_get_key_by_index( + self.0, + index, + buf.as_bytes_mut().as_mut_ptr(), + &mut len, + ); + if !success { + return if len == 0 { None } else { Some(Err(Error {})) }; + } + buf.set_len(len); + Some(Ok(buf)) + } + } + + /// Returns a copy of the value at the given index. Returns [`None`] if the index is + /// out of bounds. Any changes to it are only perceived if it's stored back. + #[inline] + pub fn get_value_by_index(&self, index: u64) -> Option { + // SAFETY: The settings map handle is valid. We do proper error handling + // afterwards. + unsafe { sys::settings_map_get_value_by_index(self.0, index).map(Value) } + } + + /// Returns an iterator over the key value pairs of the map. Every value is + /// a copy, so any changes to them are only perceived if they are stored + /// back. The iterator is double-ended, so it can be iterated backwards as + /// well. While it's possible to modify the map while iterating over it, + /// it's not recommended to do so, as the iterator might skip pairs or + /// return duplicates. In that case it's better to clone the map before and + /// iterate over the clone. + #[cfg(feature = "alloc")] + #[inline] + pub fn iter(&self) -> impl DoubleEndedIterator + '_ { + (0..self.len()).flat_map(|i| Some((self.get_key_by_index(i)?, self.get_value_by_index(i)?))) + } + + /// Returns an iterator over the key value pairs of the map. The keys are + /// returned as [`ArrayString`]. The iterator yields an error for every key + /// that does not fit into the array string. Every value is a copy, so any + /// changes to them are only perceived if they are stored back. The iterator is + /// double-ended, so it can be iterated backwards as well. While it's + /// possible to modify the map while iterating over it, it's not recommended + /// to do so, as the iterator might skip pairs or return duplicates. In that + /// case it's better to clone the map before and iterate over the clone. + #[inline] + pub fn iter_array_string( + &self, + ) -> impl DoubleEndedIterator, Error>, Value)> + '_ { + (0..self.len()).flat_map(|i| { + Some(( + self.get_key_by_index_array_string::(i)?, + self.get_value_by_index(i)?, + )) + }) + } + + /// Returns an iterator over the keys of the map. The iterator is + /// double-ended, so it can be iterated backwards as well. While it's + /// possible to modify the map while iterating over it, it's not recommended + /// to do so, as the iterator might skip keys or return duplicates. In that + /// case it's better to clone the map before and iterate over the clone.s + #[cfg(feature = "alloc")] + #[inline] + pub fn keys(&self) -> impl DoubleEndedIterator + '_ { + (0..self.len()).flat_map(|i| self.get_key_by_index(i)) + } + + /// Returns an iterator over the keys of the map. The keys are returned as + /// [`ArrayString`]. The iterator yields an error for every key that does + /// not fit into the array string. The iterator is double-ended, so it can + /// be iterated backwards as well. While it's possible to modify the map + /// while iterating over it, it's not recommended to do so, as the iterator + /// might skip keys or return duplicates. In that case it's better to clone + /// the map before and iterate over the clone. + #[inline] + pub fn keys_array_string( + &self, + ) -> impl DoubleEndedIterator, Error>> + '_ { + (0..self.len()).flat_map(|i| self.get_key_by_index_array_string(i)) + } + + /// Returns an iterator over the values of the map. Every value is a copy, + /// so any changes to them are only perceived if they are stored back. The + /// iterator is double-ended, so it can be iterated backwards as well. While + /// it's possible to modify the map while iterating over it, it's not + /// recommended to do so, as the iterator might skip values or return + /// duplicate values. In that case it's better to clone the map before and + /// iterate over the clone. + #[inline] + pub fn values(&self) -> impl DoubleEndedIterator + '_ { + (0..self.len()).flat_map(|i| self.get_value_by_index(i)) + } } diff --git a/src/runtime/settings/mod.rs b/src/runtime/settings/mod.rs index 07d9dd9..1bb8d0f 100644 --- a/src/runtime/settings/mod.rs +++ b/src/runtime/settings/mod.rs @@ -55,9 +55,11 @@ //! Check the [`Map`](struct@Map) struct for more information. pub mod gui; +mod list; mod map; mod value; pub use gui::Gui; +pub use list::*; pub use map::*; pub use value::*; diff --git a/src/runtime/settings/value.rs b/src/runtime/settings/value.rs index d675314..d02fe5f 100644 --- a/src/runtime/settings/value.rs +++ b/src/runtime/settings/value.rs @@ -1,17 +1,57 @@ -use core::mem::MaybeUninit; +use core::{fmt, mem::MaybeUninit}; use arrayvec::ArrayString; use crate::{runtime::sys, Error}; -use super::Map; +use super::{List, Map}; /// A value of a setting. This can be a value of any type that a setting can -/// hold. Currently booleans, strings and maps are supported. -#[derive(Debug)] +/// hold. Currently this is either a [`Map`], a [`List`], a [`bool`], an +/// [`i64`], an [`f64`], or a string. #[repr(transparent)] pub struct Value(pub(super) sys::SettingValue); +impl fmt::Debug for Value { + #[allow(clippy::collapsible_match)] + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + // TODO: Do a type check first. + if let Some(v) = self.get_map() { + fmt::Debug::fmt(&v, f) + } else if let Some(v) = self.get_list() { + fmt::Debug::fmt(&v, f) + } else if let Some(v) = self.get_bool() { + fmt::Debug::fmt(&v, f) + } else if let Some(v) = self.get_i64() { + fmt::Debug::fmt(&v, f) + } else if let Some(v) = self.get_f64() { + fmt::Debug::fmt(&v, f) + } else { + if let Some(v) = self.get_array_string::<128>() { + if let Ok(v) = v { + return fmt::Debug::fmt(&v, f); + } + #[cfg(not(feature = "alloc"))] + return f.write_str(""); + } + #[cfg(feature = "alloc")] + if let Some(v) = self.get_string() { + return fmt::Debug::fmt(&v, f); + } + + f.write_str("") + } + } +} + +impl Clone for Value { + #[inline] + fn clone(&self) -> Self { + // SAFETY: The handle is valid, so we can safely copy it. + Self(unsafe { sys::setting_value_copy(self.0) }) + } +} + impl Value { /// 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 @@ -21,7 +61,7 @@ impl Value { value.into() } - /// Returns the value as a map if it is a map. The map is a copy, so any + /// 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 { @@ -39,7 +79,25 @@ impl Value { } } - /// Returns the value as a boolean if it is a boolean. + /// Returns the value as a [`List`] if it is a list. The list is a copy, so + /// any changes to it are not reflected in the setting value. + #[inline] + pub fn get_list(&self) -> Option { + // SAFETY: The handle is valid. We provide a valid pointer to a list. + // After the function call we check the return value and if it's true, + // the list is initialized and we can return it. We also own the list + // handle, so it's our responsibility to free it. + unsafe { + let mut out = MaybeUninit::uninit(); + if sys::setting_value_get_list(self.0, out.as_mut_ptr()) { + Some(List(out.assume_init())) + } else { + None + } + } + } + + /// Returns the value as a [`bool`] if it is a boolean. #[inline] pub fn get_bool(&self) -> Option { // SAFETY: The handle is valid. We provide a valid pointer to a boolean. @@ -55,7 +113,39 @@ impl Value { } } - /// Returns the value as a string if it is a string. + /// Returns the value as an [`i64`] if it is a 64-bit signed integer. + #[inline] + pub fn get_i64(&self) -> Option { + // SAFETY: The handle is valid. We provide a valid pointer to a i64. + // After the function call we check the return value and if it's true, + // the i64 is initialized and we can return it. + unsafe { + let mut out = MaybeUninit::uninit(); + if sys::setting_value_get_i64(self.0, out.as_mut_ptr()) { + Some(out.assume_init()) + } else { + None + } + } + } + + /// Returns the value as an [`f64`] if it is a 64-bit floating point number. + #[inline] + pub fn get_f64(&self) -> Option { + // SAFETY: The handle is valid. We provide a valid pointer to a f64. + // After the function call we check the return value and if it's true, + // the f64 is initialized and we can return it. + unsafe { + let mut out = MaybeUninit::uninit(); + if sys::setting_value_get_f64(self.0, out.as_mut_ptr()) { + Some(out.assume_init()) + } else { + None + } + } + } + + /// Returns the value as a [`String`](alloc::string::String) if it is a string. #[cfg(feature = "alloc")] #[inline] pub fn get_string(&self) -> Option { @@ -80,9 +170,9 @@ impl Value { } } - /// Returns the value as an array backed string if it is a string. Returns - /// an error if the string is too long. The constant `N` determines the - /// maximum length of the string in bytes. + /// Returns the value as an [`ArrayString`] if it is a string. Returns an + /// error if the string is too long. The constant `N` determines the maximum + /// length of the string in bytes. #[inline] pub fn get_array_string(&self) -> Option, Error>> { // SAFETY: The handle is valid. We provide a pointer to our buffer and @@ -124,6 +214,15 @@ impl From<&Map> for Value { } } +impl From<&List> for Value { + #[inline] + fn from(value: &List) -> Self { + // SAFETY: The handle is valid. We retain ownership of the handle, so we + // only take a reference to a List. We own the returned value now. + Self(unsafe { sys::setting_value_new_list(value.0) }) + } +} + impl From for Value { #[inline] fn from(value: bool) -> Self { @@ -132,6 +231,22 @@ impl From for Value { } } +impl From for Value { + #[inline] + fn from(value: i64) -> Self { + // SAFETY: This is always safe to call. We own the returned value now. + Self(unsafe { sys::setting_value_new_i64(value) }) + } +} + +impl From for Value { + #[inline] + fn from(value: f64) -> Self { + // SAFETY: This is always safe to call. We own the returned value now. + Self(unsafe { sys::setting_value_new_f64(value) }) + } +} + impl From<&str> for Value { #[inline] fn from(value: &str) -> Self { diff --git a/src/runtime/sys.rs b/src/runtime/sys.rs index 28961e4..e4d2b79 100644 --- a/src/runtime/sys.rs +++ b/src/runtime/sys.rs @@ -38,6 +38,10 @@ impl TimerState { #[repr(transparent)] pub struct SettingsMap(NonZeroU64); +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +#[repr(transparent)] +pub struct SettingsList(NonZeroU64); + #[derive(Clone, Copy, Debug, PartialEq, Eq)] #[repr(transparent)] pub struct SettingValue(NonZeroU64); @@ -266,6 +270,51 @@ extern "C" { key_ptr: *const u8, key_len: usize, ) -> Option; + /// Gets the length of a settings map. + pub fn settings_map_len(map: SettingsMap) -> u64; + /// Gets the key of a setting value from the settings map based on the index + /// by storing it into the buffer provided. 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 index is out of bounds. + /// The key is guaranteed to be valid UTF-8 and is not nul-terminated. + pub fn settings_map_get_key_by_index( + map: SettingsMap, + idx: u64, + buf_ptr: *mut u8, + buf_len_ptr: *mut usize, + ) -> bool; + /// Gets a copy of the setting value from the settings map based on the + /// index. Returns `None` if the index is out of bounds. Any changes to it + /// are only perceived if it's stored back. You own the setting value and + /// are responsible for freeing it. + pub fn settings_map_get_value_by_index(map: SettingsMap, idx: u64) -> Option; + + /// Creates a new settings list. You own the settings list and are + /// responsible for freeing it. + pub fn settings_list_new() -> SettingsList; + /// Frees a settings list. + pub fn settings_list_free(list: SettingsList); + /// Copies a settings list. No changes inside the copy affect the original + /// settings list. You own the new settings list and are responsible for + /// freeing it. + pub fn settings_list_copy(list: SettingsList) -> SettingsList; + /// Gets the length of a settings list. + pub fn settings_list_len(list: SettingsList) -> u64; + /// Gets a copy of the setting value from the settings list based on the + /// index. Returns `None` if the index is out of bounds. Any changes to it + /// are only perceived if it's stored back. You own the setting value and + /// are responsible for freeing it. + pub fn settings_list_get(list: SettingsList, idx: u64) -> Option; + /// Pushes a copy of the setting value to the end of the settings list. You + /// still retain ownership of the setting value, which means you still need + /// to free it. + pub fn settings_list_push(list: SettingsList, value: SettingValue); + /// Inserts a copy of the setting value into the settings list at the index + /// given. Returns `false` if the index is out of bounds. No matter what + /// happens, you still retain ownership of the setting value, which means + /// you still need to free it. + pub fn settings_list_insert(list: SettingsList, idx: u64, value: SettingValue) -> bool; /// 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 @@ -273,15 +322,31 @@ extern "C" { /// 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 setting value from a settings list. The value is a copy of + /// the settings list. Any changes to the original settings list 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 list, which means you still need to free it. + pub fn setting_value_new_list(value: SettingsList) -> 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 64-bit signed integer setting value. You own the setting + /// value and are responsible for freeing it. + pub fn setting_value_new_i64(value: i64) -> SettingValue; + /// Creates a new 64-bit floating point setting value. You own the setting + /// value and are responsible for freeing it. + pub fn setting_value_new_f64(value: f64) -> 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); + /// Copies a setting value. No changes inside the copy affect the original + /// setting value. You own the new setting value and are responsible for + /// freeing it. + pub fn setting_value_copy(value: SettingValue) -> 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 @@ -289,12 +354,31 @@ extern "C" { /// 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 setting value as a settings list by storing it into + /// the pointer provided. Returns `false` if the setting value is not a + /// settings list. 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 list and are + /// responsible for freeing it. + pub fn setting_value_get_list(value: SettingValue, value_ptr: *mut SettingsList) -> 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 64-bit signed integer setting value by storing it + /// into the pointer provided. Returns `false` if the setting value is not a + /// 64-bit signed integer. 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_i64(value: SettingValue, value_ptr: *mut i64) -> bool; + /// Gets the value of a 64-bit floating point setting value by storing it + /// into the pointer provided. Returns `false` if the setting value is not a + /// 64-bit floating point number. 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_f64(value: SettingValue, value_ptr: *mut f64) -> 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