From d6d99f3c0c46a104ad869bbececdb19549f2ced2 Mon Sep 17 00:00:00 2001 From: Christopher Serr Date: Thu, 26 Oct 2023 18:54:06 +0200 Subject: [PATCH] Add settings maps and values (#72) This adds support for interacting with the settings maps and settings values. This allows auto splitters to store additional custom settings that are not exposed to the user and also allows the auto splitters to react to changes to the settings without the auto splitter having to be restarted. --- asr-derive/src/lib.rs | 62 ++++++++---- asr-derive/src/unity.rs | 18 +++- src/emulator/gba/retroarch.rs | 16 +-- src/future/mod.rs | 3 +- src/game_engine/unity/il2cpp.rs | 4 +- src/game_engine/unity/mono.rs | 6 +- src/game_engine/unity/scene.rs | 12 +-- src/runtime/mod.rs | 6 +- .../{user_settings.rs => settings/gui.rs} | 77 +++++++++++---- src/runtime/settings/map.rs | 98 +++++++++++++++++++ src/runtime/settings/mod.rs | 9 ++ src/runtime/settings/value.rs | 50 ++++++++++ src/runtime/sys.rs | 65 ++++++++++++ 13 files changed, 358 insertions(+), 68 deletions(-) rename src/runtime/{user_settings.rs => settings/gui.rs} (52%) create mode 100644 src/runtime/settings/map.rs create mode 100644 src/runtime/settings/mod.rs create mode 100644 src/runtime/settings/value.rs diff --git a/asr-derive/src/lib.rs b/asr-derive/src/lib.rs index cc667d6..9f4b339 100644 --- a/asr-derive/src/lib.rs +++ b/asr-derive/src/lib.rs @@ -3,14 +3,15 @@ use proc_macro::TokenStream; use quote::{quote, quote_spanned}; use syn::{spanned::Spanned, Data, DeriveInput, Expr, ExprLit, Lit, Meta}; -/// Generates a `register` method for a struct that automatically registers its -/// fields as settings and returns the struct with the user's settings applied. +/// Implements the `Gui` trait for a struct that allows you to register its +/// fields as settings widgets and returns the struct with the user's settings +/// applied. /// /// # Example /// /// ```no_run -/// #[derive(Settings)] -/// struct MySettings { +/// #[derive(Gui)] +/// struct Settings { /// /// General Settings /// _general_settings: Title, /// /// Use Game Time @@ -20,19 +21,17 @@ use syn::{spanned::Spanned, Data, DeriveInput, Expr, ExprLit, Lit, Meta}; /// } /// ``` /// -/// This will generate the following code: +/// The type can then be used like so: /// /// ```no_run -/// impl MySettings { -/// pub fn register() -> Self { -/// asr::user_settings::add_title("_general_settings", "General Settings", 0); -/// let use_game_time = asr::user_settings::add_bool("use_game_time", "Use Game Time", false); -/// asr::user_settings::set_tooltip("use_game_time", "This is the tooltip."); -/// Self { use_game_time } -/// } +/// let mut settings = Settings::register(); +/// +/// loop { +/// settings.update(); +/// // Do something with the settings. /// } /// ``` -#[proc_macro_derive(Settings, attributes(default, heading_level))] +#[proc_macro_derive(Gui, attributes(default, heading_level))] pub fn settings_macro(input: TokenStream) -> TokenStream { let ast: DeriveInput = syn::parse(input).unwrap(); @@ -58,14 +57,21 @@ pub fn settings_macro(input: TokenStream) -> TokenStream { let mut tooltip_string = String::new(); let mut is_in_tooltip = false; for attr in &field.attrs { - let Meta::NameValue(nv) = &attr.meta else { continue }; - let Some(ident) = nv.path.get_ident() else { continue }; + let Meta::NameValue(nv) = &attr.meta else { + continue; + }; + let Some(ident) = nv.path.get_ident() else { + continue; + }; if ident != "doc" { continue; } let Expr::Lit(ExprLit { lit: Lit::Str(s), .. - }) = &nv.value else { continue }; + }) = &nv.value + else { + continue; + }; let value = s.value(); let value = value.trim(); let target_string = if is_in_tooltip { @@ -102,7 +108,9 @@ pub fn settings_macro(input: TokenStream) -> TokenStream { .attrs .iter() .filter_map(|x| { - let Meta::NameValue(nv) = &x.meta else { return None }; + let Meta::NameValue(nv) = &x.meta else { + return None; + }; let span = nv.span(); if nv.path.is_ident("default") { let value = &nv.value; @@ -119,18 +127,30 @@ pub fn settings_macro(input: TokenStream) -> TokenStream { } quote! { - impl #struct_name { - pub fn register() -> Self { + impl asr::settings::Gui for #struct_name { + fn register() -> Self { Self { #(#field_names: { - let mut args = <#field_tys as asr::user_settings::Setting>::Args::default(); + let mut args = <#field_tys as asr::settings::gui::Widget>::Args::default(); #args_init - let mut value = asr::user_settings::Setting::register(#field_name_strings, #field_descs, args); + let mut value = asr::settings::gui::Widget::register(#field_name_strings, #field_descs, args); #field_tooltips value },)* } } + + fn update_from(&mut self, settings_map: &asr::settings::Map) { + #({ + let mut args = <#field_tys as asr::settings::gui::Widget>::Args::default(); + #args_init + asr::settings::gui::Widget::update_from(&mut self.#field_names, settings_map, #field_name_strings, args); + })* + } + + fn update(&mut self) { + self.update_from(&asr::settings::Map::load()); + } } } .into() diff --git a/asr-derive/src/unity.rs b/asr-derive/src/unity.rs index a6a4b86..2c33649 100644 --- a/asr-derive/src/unity.rs +++ b/asr-derive/src/unity.rs @@ -26,7 +26,9 @@ pub fn process(input: TokenStream, mono_module: impl ToTokens) -> TokenStream { let field_name = field.ident.clone().unwrap(); let span = field_name.span(); let is_static = field.attrs.iter().any(|x| { - let Meta::Path(path) = &x.meta else { return false }; + let Meta::Path(path) = &x.meta else { + return false; + }; path.is_ident("static_field") }); field_reads.push(if is_static { @@ -42,13 +44,19 @@ pub fn process(input: TokenStream, mono_module: impl ToTokens) -> TokenStream { .attrs .iter() .find_map(|x| { - let Meta::NameValue(name_value) = &x.meta else { return None }; + let Meta::NameValue(name_value) = &x.meta else { + return None; + }; if !name_value.path.is_ident("rename") { return None; } let Expr::Lit(ExprLit { - lit: Lit::Str(name), .. - }) = &name_value.value else { return None }; + lit: Lit::Str(name), + .. + }) = &name_value.value + else { + return None; + }; Some(name.value()) }) .unwrap_or_else(|| field.ident.clone().unwrap().to_string()); @@ -97,7 +105,7 @@ pub fn process(input: TokenStream, mono_module: impl ToTokens) -> TokenStream { let class = image.wait_get_class(process, module, #stuct_name_string).await; #( - let #field_names = class.wait_get_field(process, module, #lookup_names).await; + let #field_names = class.wait_get_field_offset(process, module, #lookup_names).await; )* #binding_name { diff --git a/src/emulator/gba/retroarch.rs b/src/emulator/gba/retroarch.rs index a4a9156..c708ce7 100644 --- a/src/emulator/gba/retroarch.rs +++ b/src/emulator/gba/retroarch.rs @@ -1,6 +1,4 @@ -use crate::{ - file_format::pe, signature::Signature, Address, Address32, Address64, Process, -}; +use crate::{file_format::pe, signature::Signature, Address, Address32, Address64, Process}; #[derive(Copy, Clone, Debug, PartialEq, Eq)] pub struct State { @@ -32,8 +30,10 @@ impl State { self.core_base = core_address; match core_name { - "vbam_libretro.dll" | "vba_next_libretro.dll" | "mednafen_gba_libretro.dll" => self.vba(game, is_64_bit, core_name), - "mgba_libretro.dll" => super::mgba::State::find_ram(&mut super::mgba::State, game), + "vbam_libretro.dll" | "vba_next_libretro.dll" | "mednafen_gba_libretro.dll" => { + self.vba(game, is_64_bit, core_name) + } + "mgba_libretro.dll" => super::mgba::State::find_ram(&super::mgba::State, game), "gpsp_libretro.dll" => self.gpsp(game, is_64_bit, core_name), _ => None, } @@ -70,8 +70,8 @@ impl State { return None; } } - - addr + + addr }; let ewram = game.read::(ewram_pointer).ok()?; @@ -93,7 +93,7 @@ impl State { let ptr = SIG2.scan_process_range(game, module_range)?; game.read::(ptr + 1).ok()?.into() }; - + let ewram = game.read::(ewram_pointer).ok()?; let iwram = game.read::(iwram_pointer).ok()?; diff --git a/src/future/mod.rs b/src/future/mod.rs index 2106841..1fa1c0e 100644 --- a/src/future/mod.rs +++ b/src/future/mod.rs @@ -413,8 +413,7 @@ macro_rules! async_main { let size = mem::size_of::(); const PAGE_SIZE: usize = 64 << 10; assert!(mem::align_of::() <= PAGE_SIZE); - // TODO: div_ceil - let pages = (size + (PAGE_SIZE - 1)) / PAGE_SIZE; + let pages = size.div_ceil(PAGE_SIZE); #[cfg(target_arch = "wasm32")] let old_page_count = core::arch::wasm32::memory_grow(0, pages); diff --git a/src/game_engine/unity/il2cpp.rs b/src/game_engine/unity/il2cpp.rs index 47daa26..4361ad0 100644 --- a/src/game_engine/unity/il2cpp.rs +++ b/src/game_engine/unity/il2cpp.rs @@ -320,7 +320,7 @@ impl Image { } } -/// A .NET class that is part of an [`Image`](Image). +/// A .NET class that is part of an [`Image`]. pub struct Class { class: Address, } @@ -420,7 +420,7 @@ impl Class { /// the offset of the field from the start of an instance of the class. If /// it's a static field, the offset will be from the start of the static /// table. This is the `await`able version of the - /// [`get_field`](Self::get_field) function. + /// [`get_field_offset`](Self::get_field_offset) function. pub async fn wait_get_field_offset( &self, process: &Process, diff --git a/src/game_engine/unity/mono.rs b/src/game_engine/unity/mono.rs index 59488dc..6e79dad 100644 --- a/src/game_engine/unity/mono.rs +++ b/src/game_engine/unity/mono.rs @@ -316,7 +316,7 @@ impl Image { } } -/// A .NET class that is part of an [`Image`](Image). +/// A .NET class that is part of an [`Image`]. pub struct Class { class: Address, } @@ -464,7 +464,7 @@ impl Class { /// the offset of the field from the start of an instance of the class. If /// it's a static field, the offset will be from the start of the static /// table. This is the `await`able version of the - /// [`get_field`](Self::get_field) function. + /// [`get_field_offset`](Self::get_field_offset) function. pub async fn wait_get_field_offset( &self, process: &Process, @@ -838,7 +838,7 @@ fn detect_version(process: &Process) -> Option { const SIG_202X: Signature<6> = Signature::new("00 32 30 32 ?? 2E"); let Some(addr) = SIG_202X.scan_process_range(process, unity_module) else { - return Some(Version::V2) + return Some(Version::V2); }; const ZERO: u8 = b'0'; diff --git a/src/game_engine/unity/scene.rs b/src/game_engine/unity/scene.rs index c16eef7..ec99d8d 100644 --- a/src/game_engine/unity/scene.rs +++ b/src/game_engine/unity/scene.rs @@ -124,9 +124,8 @@ impl SceneManager { self.get_current_scene(process)?.index(process, self) } - /// Returns the full path to the current scene. Use - /// [`get_scene_name`](Self::get_scene_name) afterwards to - /// get the scene name. + /// Returns the full path to the current scene. Use [`get_scene_name`] + /// afterwards to get the scene name. pub fn get_current_scene_path( &self, process: &Process, @@ -336,9 +335,10 @@ impl Transform { let name_ptr = { match scene_manager.is_il2cpp { true => { - let Ok(name_ptr) = scene_manager - .read_pointer(process, vtable + 2_u64.wrapping_mul(scene_manager.pointer_size() as _)) - else { + let Ok(name_ptr) = scene_manager.read_pointer( + process, + vtable + 2_u64.wrapping_mul(scene_manager.pointer_size() as _), + ) else { return false; }; diff --git a/src/runtime/mod.rs b/src/runtime/mod.rs index ce51ce4..21164a1 100644 --- a/src/runtime/mod.rs +++ b/src/runtime/mod.rs @@ -5,8 +5,8 @@ mod memory_range; mod process; mod sys; +pub mod settings; pub mod timer; -pub mod user_settings; /// An error returned by a runtime function. #[derive(Debug)] @@ -56,7 +56,7 @@ pub fn get_os() -> Result, Error> { let mut buf = arrayvec::ArrayString::<16>::new(); // SAFETY: We provide a valid pointer and length to the buffer. We check // whether the buffer was successfully filled and set the length of the - // buffer accordingly. + // buffer accordingly. The buffer is guaranteed to be valid UTF-8. unsafe { let mut len = buf.capacity(); let success = sys::runtime_get_os(buf.as_mut_ptr(), &mut len); @@ -80,7 +80,7 @@ pub fn get_arch() -> Result, Error> { let mut buf = arrayvec::ArrayString::<16>::new(); // SAFETY: We provide a valid pointer and length to the buffer. We check // whether the buffer was successfully filled and set the length of the - // buffer accordingly. + // buffer accordingly. The buffer is guaranteed to be valid UTF-8. unsafe { let mut len = buf.capacity(); let success = sys::runtime_get_arch(buf.as_mut_ptr(), &mut len); diff --git a/src/runtime/user_settings.rs b/src/runtime/settings/gui.rs similarity index 52% rename from src/runtime/user_settings.rs rename to src/runtime/settings/gui.rs index b090cc0..03134b5 100644 --- a/src/runtime/user_settings.rs +++ b/src/runtime/settings/gui.rs @@ -1,13 +1,17 @@ -//! This module allows you to add settings that the user can modify. - -use super::sys; +//! This module allows you to add settings widgets to the settings GUI that the +//! user can modify. #[cfg(feature = "derive")] -pub use asr_derive::Settings; +pub use asr_derive::Gui; + +use crate::runtime::sys; -/// 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 +use super::map::Map; + +/// Adds a new boolean setting widget to the settings GUI 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 in the global +/// settings [`Map`](super::Map) and needs to be unique across all types of /// settings. #[inline] pub fn add_bool(key: &str, description: &str, default_value: bool) -> bool { @@ -24,7 +28,7 @@ pub fn add_bool(key: &str, description: &str, default_value: bool) -> bool { } } -/// Adds a new title to the user settings. This is used to group settings +/// Adds a new title widget to the settings GUI. 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. @@ -43,30 +47,55 @@ pub fn add_title(key: &str, description: &str, heading_level: u32) { } } -/// Adds a tooltip to a setting based on its key. A tooltip is useful for +/// Adds a tooltip to a setting widget based on its key. A tooltip is useful for /// explaining the purpose of a setting to the user. #[inline] pub fn set_tooltip(key: &str, tooltip: &str) { - // SAFETY: We provide valid pointers and lengths to key and description. - // They are also guaranteed to be valid UTF-8 strings. + // SAFETY: We provide valid pointers and lengths to key and tooltip. They + // are also guaranteed to be valid UTF-8 strings. unsafe { sys::user_settings_set_tooltip(key.as_ptr(), key.len(), tooltip.as_ptr(), tooltip.len()) } } -/// A type that can be registered as a user setting. This is an internal trait -/// that you don't need to worry about. -pub trait Setting { - /// The arguments that are needed to register the setting. +/// A trait that can be derived to describe an entire settings GUI through a +/// struct declaration. Check the derive macro [`Gui`](macro@Gui) for more +/// information. +pub trait Gui { + /// Registers the settings by adding all the widgets to the settings GUI and + /// initializing the settings with the values that the user has set or their + /// default values if they haven't been modified yet. + fn register() -> Self; + + /// Updates the settings with the values that the user has set from the + /// currently set global settings map. + fn update(&mut self) { + self.update_from(&Map::load()); + } + + /// Updates the settings with the values that the user has set from the + /// settings map provided. + fn update_from(&mut self, settings_map: &Map); +} + +/// A settings widget that can be added to the settings GUI. This is an internal +/// trait that you don't need to worry about. +#[doc(hidden)] +pub trait Widget { + /// The arguments that are needed to register the widget. type Args: Default; - /// Registers the setting with the given key, description and default value. + /// Registers the widget with the given key, description and default value. /// Returns the value that the user has set or the default value if the user /// did not set a value. fn register(key: &str, description: &str, args: Self::Args) -> Self; + /// Updates the value of the setting based on the value that the user has + /// set in the provided settings map. + fn update_from(&mut self, settings_map: &Map, key: &str, args: Self::Args); } /// The arguments that are needed to register a boolean setting. This is an /// internal type that you don't need to worry about. +#[doc(hidden)] #[derive(Default)] #[non_exhaustive] pub struct BoolArgs { @@ -74,13 +103,21 @@ pub struct BoolArgs { pub default: bool, } -impl Setting for bool { +impl Widget for bool { type Args = BoolArgs; #[inline] fn register(key: &str, description: &str, args: Self::Args) -> Self { add_bool(key, description, args.default) } + + #[inline] + fn update_from(&mut self, settings_map: &Map, key: &str, args: Self::Args) { + *self = settings_map + .get(key) + .and_then(|value| value.get_bool()) + .unwrap_or(args.default); + } } /// A title that can be used to group settings together. @@ -88,6 +125,7 @@ pub struct Title; /// The arguments that are needed to register a title. This is an internal type /// that you don't need to worry about. +#[doc(hidden)] #[derive(Default)] #[non_exhaustive] pub struct TitleArgs { @@ -96,7 +134,7 @@ pub struct TitleArgs { pub heading_level: u32, } -impl Setting for Title { +impl Widget for Title { type Args = TitleArgs; #[inline] @@ -104,4 +142,7 @@ impl Setting for Title { add_title(key, description, args.heading_level); Self } + + #[inline] + fn update_from(&mut self, _settings_map: &Map, _key: &str, _args: Self::Args) {} } diff --git a/src/runtime/settings/map.rs b/src/runtime/settings/map.rs new file mode 100644 index 0000000..104191c --- /dev/null +++ b/src/runtime/settings/map.rs @@ -0,0 +1,98 @@ +use crate::runtime::sys; + +use super::Value; + +/// A map consisting of settings that are configured. Every setting has a string +/// based key and a [`Value`]. There is a global settings map that represents +/// all the settings that the user has configured at the given time. Settings +/// that are unmodified are usually not stored in the map. The global map is +/// what gets stored to disk and loaded from disk. Any changes made in the +/// settings GUI will be reflected in the global map and vice versa. The key of +/// 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 Drop for Map { + #[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_map_free(self.0) } + } +} + +impl Clone for Map { + #[inline] + fn clone(&self) -> Self { + // SAFETY: The handle is valid, so we can safely copy it. + Self(unsafe { sys::settings_map_copy(self.0) }) + } +} + +impl Default for Map { + #[inline] + fn default() -> Self { + Self::new() + } +} + +impl Map { + /// Creates a new empty settings map. + #[inline] + pub fn new() -> Self { + // SAFETY: This is always safe to call. + Self(unsafe { sys::settings_map_new() }) + } + + /// Loads a copy of the currently set global settings map. Any changes to it + /// are only perceived if it's stored back. + #[inline] + pub fn load() -> Self { + // SAFETY: This is always safe to call. + Self(unsafe { sys::settings_map_load() }) + } + + /// Stores a copy of the settings map as the new global settings map. This + /// will overwrite the previous global settings map. There's a chance that + /// the settings map was changed in the meantime, so those changes could get + /// lost. Prefer using [`store_if_unchanged`](Self::store_if_unchanged) if + /// you want to avoid that. + #[inline] + pub fn store(&self) { + // SAFETY: The handle is valid, so it can be stored. + unsafe { sys::settings_map_store(self.0) } + } + + /// Stores a copy of the new settings map as the new global settings map if + /// the map has not changed in the meantime. This is done by comparing the + /// old map. Returns [`true`] if the map was stored successfully. + /// Returns [`false`] if the map was changed in the meantime. + #[inline] + pub fn store_if_unchanged(&self, old: &Self) -> bool { + // SAFETY: Both handles are valid. + unsafe { sys::settings_map_store_if_unchanged(old.0, self.0) } + } + + /// Inserts a copy of the setting value into the settings map based on the + /// key. If the key already exists, it will be overwritten. + #[inline] + pub fn insert(&self, key: &str, value: &Value) { + // SAFETY: The settings map handle is valid. We provide a valid pointer + // and length to the key which is guaranteed to be valid UTF-8. The + // setting value handle is also valid. + unsafe { sys::settings_map_insert(self.0, key.as_ptr(), key.len(), value.0) } + } + + /// 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. + #[inline] + pub fn get(&self, key: &str) -> Option { + // SAFETY: The settings map handle is valid. We provide a valid pointer + // 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) } + } +} diff --git a/src/runtime/settings/mod.rs b/src/runtime/settings/mod.rs new file mode 100644 index 0000000..318b496 --- /dev/null +++ b/src/runtime/settings/mod.rs @@ -0,0 +1,9 @@ +//! Support for interacting with the settings of the auto splitter. + +pub mod gui; +mod map; +mod value; + +pub use gui::Gui; +pub use map::*; +pub use value::*; diff --git a/src/runtime/settings/value.rs b/src/runtime/settings/value.rs new file mode 100644 index 0000000..d80e66a --- /dev/null +++ b/src/runtime/settings/value.rs @@ -0,0 +1,50 @@ +use core::mem::MaybeUninit; + +use crate::runtime::sys; + +/// A value of a setting. This can be a value of any type that a setting can +/// hold. Currently only boolean settings 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. + #[inline] + pub fn new(value: impl Into) -> Self { + value.into() + } + + /// Returns the value as a boolean 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. + // After the function call we check the return value and if it's true, + // the boolean is initialized and we can return it. + unsafe { + let mut out = MaybeUninit::uninit(); + if sys::setting_value_get_bool(self.0, out.as_mut_ptr()) { + Some(out.assume_init()) + } else { + None + } + } + } +} + +impl From for Value { + #[inline] + fn from(value: bool) -> Self { + // SAFETY: This is always safe to call. + Self(unsafe { sys::setting_value_new_bool(value) }) + } +} diff --git a/src/runtime/sys.rs b/src/runtime/sys.rs index 873f181..42be7e4 100644 --- a/src/runtime/sys.rs +++ b/src/runtime/sys.rs @@ -34,6 +34,14 @@ impl TimerState { pub const ENDED: Self = Self(3); } +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +#[repr(transparent)] +pub struct SettingsMap(NonZeroU64); + +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +#[repr(transparent)] +pub struct SettingValue(NonZeroU64); + extern "C" { /// Gets the state that the timer currently is in. pub fn timer_get_state() -> TimerState; @@ -177,4 +185,61 @@ extern "C" { tooltip_ptr: *const u8, tooltip_len: usize, ); + + /// Creates a new settings map. You own the settings map and are responsible + /// for freeing it. + pub fn settings_map_new() -> SettingsMap; + /// Frees a settings map. + pub fn settings_map_free(map: SettingsMap); + /// Loads a copy of the currently set global settings map. Any changes to it + /// are only perceived if it's stored back. You own the settings map and are + /// responsible for freeing it. + pub fn settings_map_load() -> SettingsMap; + /// Stores a copy of the settings map as the new global settings map. This + /// will overwrite the previous global settings map. You still retain + /// ownership of the map, which means you still need to free it. There's a + /// chance that the settings map was changed in the meantime, so those + /// changes could get lost. Prefer using `settings_map_store_if_unchanged` + /// if you want to avoid that. + pub fn settings_map_store(map: SettingsMap); + /// Stores a copy of the new settings map as the new global settings map if + /// the map has not changed in the meantime. This is done by comparing the + /// old map. You still retain ownership of both maps, which means you still + /// need to free them. Returns `true` if the map was stored successfully. + /// Returns `false` if the map was changed in the meantime. + pub fn settings_map_store_if_unchanged(old_map: SettingsMap, new_map: SettingsMap) -> bool; + /// Copies a settings map. No changes inside the copy affect the original + /// settings map. You own the new settings map and are responsible for + /// freeing it. + pub fn settings_map_copy(map: SettingsMap) -> SettingsMap; + /// 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. + pub fn settings_map_insert( + map: SettingsMap, + key_ptr: *const u8, + key_len: usize, + value: SettingValue, + ); + /// 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. + pub fn settings_map_get( + map: SettingsMap, + key_ptr: *const u8, + key_len: usize, + ) -> Option; + + /// 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; + /// Frees a setting value. + pub fn setting_value_free(value: SettingValue); + /// 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; }