From 68f80648a7f0d9e06ae2a8e7c1acc9acc3e59dc3 Mon Sep 17 00:00:00 2001 From: Christopher Serr Date: Wed, 25 Oct 2023 23:30:30 +0200 Subject: [PATCH] Add API for accessing / modify settings maps (#734) This adds a new API that allows auto splitters to access the current settings map and to modify it. For now only booleans settings are supported, but this can very easily be extended to other types. The API is designed so no large scale locking is necessary, so malicious or buggy auto splitters can't lock up the application. However, in order for the API to not cause race conditions all the values are based on the copy-on-write principle. The values are cheaply cloned, allowing the auto splitter to work on a seemingly owned copy of the settings map and the values inside. A CAS algorithm can be used to then resolve any races where two parts of the code make modifications to the settings map in parallel. Co-authored-by: AlexKnauth --- crates/livesplit-auto-splitting/README.md | 63 ++++ crates/livesplit-auto-splitting/src/lib.rs | 65 +++- .../livesplit-auto-splitting/src/runtime.rs | 280 ++++++++++++++++-- .../livesplit-auto-splitting/src/settings.rs | 21 +- src/auto_splitting/mod.rs | 77 ++++- 5 files changed, 459 insertions(+), 47 deletions(-) diff --git a/crates/livesplit-auto-splitting/README.md b/crates/livesplit-auto-splitting/README.md index 92695d13..c315be3a 100644 --- a/crates/livesplit-auto-splitting/README.md +++ b/crates/livesplit-auto-splitting/README.md @@ -39,6 +39,12 @@ pub struct Process(NonZeroU64); #[repr(transparent)] pub struct ProcessId(u64); +#[repr(transparent)] +pub struct SettingsMap(NonZeroU64); + +#[repr(transparent)] +pub struct SettingValue(NonZeroU64); + #[repr(transparent)] pub struct TimerState(u32); @@ -226,6 +232,63 @@ 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; } ``` diff --git a/crates/livesplit-auto-splitting/src/lib.rs b/crates/livesplit-auto-splitting/src/lib.rs index 12bc9004..66f6afd5 100644 --- a/crates/livesplit-auto-splitting/src/lib.rs +++ b/crates/livesplit-auto-splitting/src/lib.rs @@ -40,6 +40,12 @@ //! pub struct ProcessId(u64); //! //! #[repr(transparent)] +//! pub struct SettingsMap(NonZeroU64); +//! +//! #[repr(transparent)] +//! pub struct SettingValue(NonZeroU64); +//! +//! #[repr(transparent)] //! pub struct TimerState(u32); //! //! impl TimerState { @@ -226,6 +232,63 @@ //! 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; //! } //! ``` //! @@ -261,6 +324,6 @@ mod timer; pub use process::Process; pub use runtime::{Config, CreationError, InterruptHandle, Runtime, RuntimeGuard}; -pub use settings::{SettingValue, SettingsStore, UserSetting, UserSettingKind}; +pub use settings::{SettingValue, SettingsMap, UserSetting, UserSettingKind}; pub use time; pub use timer::{Timer, TimerState}; diff --git a/crates/livesplit-auto-splitting/src/runtime.rs b/crates/livesplit-auto-splitting/src/runtime.rs index 5028d034..062ffb05 100644 --- a/crates/livesplit-auto-splitting/src/runtime.rs +++ b/crates/livesplit-auto-splitting/src/runtime.rs @@ -4,7 +4,7 @@ use crate::{ process::{build_path, Process}, settings::UserSetting, timer::Timer, - SettingValue, SettingsStore, UserSettingKind, + SettingValue, SettingsMap, UserSettingKind, }; use anyhow::{ensure, format_err, Context as _, Result}; @@ -80,10 +80,14 @@ pub enum CreationError { slotmap::new_key_type! { struct ProcessKey; + struct SettingsMapKey; + struct SettingValueKey; } pub struct Context { processes: SlotMap, + settings_maps: SlotMap, + setting_values: SlotMap, user_settings: Arc>, shared_data: Arc, timer: T, @@ -163,11 +167,11 @@ impl ProcessList { /// The configuration to use when creating a new [`Runtime`]. #[non_exhaustive] pub struct Config<'a> { - /// The settings store that is used to store the settings of the auto + /// The settings map that is used to store the settings of the auto /// splitter. This contains all the settings that are currently modified by /// the user. It may not contain all the settings that are registered as /// user settings, because the user may not have modified them yet. - pub settings_store: Option, + pub settings_map: Option, /// The auto splitter itself may be a runtime that wants to load a script /// from a file to interpret. This is the path to that script. It is /// provided to the auto splitter as the `SCRIPT_PATH` environment variable. @@ -189,7 +193,7 @@ pub struct Config<'a> { impl Default for Config<'_> { fn default() -> Self { Self { - settings_store: None, + settings_map: None, interpreter_script_path: None, debug_info: false, optimize: true, @@ -199,7 +203,7 @@ impl Default for Config<'_> { } struct SharedData { - settings_store: Mutex, + settings_map: Mutex, tick_rate: Mutex, } @@ -271,6 +275,30 @@ impl RuntimeGuard<'_, T> { pub fn attached_processes(&self) -> impl Iterator { self.data.store.data().processes.values() } + + /// Returns the total amount of handles that are currently in use. This may + /// be useful for debugging purposes to detect leaked handles. + pub fn handles(&self) -> u64 { + let data = self.data.store.data(); + data.processes.len() as u64 + + data.settings_maps.len() as u64 + + data.setting_values.len() as u64 + } +} + +impl SharedData { + fn set_settings_map(&self, settings_map: SettingsMap) { + *self.settings_map.lock().unwrap() = settings_map; + } + + fn set_settings_map_if_unchanged(&self, old: &SettingsMap, new: SettingsMap) -> bool { + let mut guard = self.settings_map.lock().unwrap(); + let success = guard.is_unchanged(old); + if success { + *guard = new; + } + success + } } impl Runtime { @@ -302,7 +330,7 @@ impl Runtime { let user_settings = Arc::new(Vec::new()); let shared_data = Arc::new(SharedData { - settings_store: Mutex::new(config.settings_store.unwrap_or_default()), + settings_map: Mutex::new(config.settings_map.unwrap_or_default()), tick_rate: Mutex::new(Duration::new(0, 1_000_000_000 / 120)), }); @@ -310,6 +338,8 @@ impl Runtime { &engine, Context { processes: SlotMap::with_key(), + settings_maps: SlotMap::with_key(), + setting_values: SlotMap::with_key(), user_settings: user_settings.clone(), shared_data: shared_data.clone(), timer, @@ -409,30 +439,25 @@ impl Runtime { /// Accesses a copy of the currently stored settings. The auto splitter can /// change these at any time. If you intend to make modifications to the - /// settings, you need to set them again via [`set_settings_store`] or - /// [`set_settings_store_if_unchanged`]. - pub fn settings_store(&self) -> SettingsStore { - self.shared_data.settings_store.lock().unwrap().clone() + /// settings, you need to set them again via [`set_settings_map`] or + /// [`set_settings_map_if_unchanged`]. + pub fn settings_map(&self) -> SettingsMap { + self.shared_data.settings_map.lock().unwrap().clone() } - /// Unconditionally sets the settings store. - pub fn set_settings_store(&self, settings_store: SettingsStore) { - *self.shared_data.settings_store.lock().unwrap() = settings_store; + /// Unconditionally sets the settings map. + pub fn set_settings_map(&self, settings_map: SettingsMap) { + self.shared_data.set_settings_map(settings_map) } - /// Sets the settings store if it didn't change in the meantime. Returns + /// Sets the settings map if it didn't change in the meantime. Returns /// [`true`] if it got set and [`false`] if it didn't. The auto splitter may - /// by itself change the settings store within each update. So changing the + /// by itself change the settings map within each update. So changing the /// settings from outside may race the auto splitter. You may use this to /// reapply the changes if the auto splitter changed the settings in the /// meantime. - pub fn set_settings_store_if_unchanged(&self, old: SettingsStore, new: SettingsStore) -> bool { - let mut guard = self.shared_data.settings_store.lock().unwrap(); - let success = guard.is_unchanged(&old); - if success { - *guard = new; - } - success + pub fn set_settings_map_if_unchanged(&self, old: &SettingsMap, new: SettingsMap) -> bool { + self.shared_data.set_settings_map_if_unchanged(old, new) } /// Accesses all the settings that are meant to be shown to and modified by @@ -1014,18 +1039,18 @@ fn bind_interface(linker: &mut Linker>) -> Result<(), Creat let key = Arc::::from(get_str(memory, key_ptr, key_len)?); let description = get_str(memory, description_ptr, description_len)?.into(); let default_value = default_value != 0; - let value_in_store = - match context.shared_data.settings_store.lock().unwrap().get(&key) { - Some(SettingValue::Bool(v)) => *v, - None => default_value, - }; + let value_in_map = match context.shared_data.settings_map.lock().unwrap().get(&key) + { + Some(SettingValue::Bool(v)) => *v, + _ => default_value, + }; Arc::make_mut(&mut context.user_settings).push(UserSetting { key, description, tooltip: None, kind: UserSettingKind::Bool { default_value }, }); - Ok(value_in_store as u32) + Ok(value_in_map as u32) } }) .map_err(|source| CreationError::LinkFunction { @@ -1075,6 +1100,205 @@ fn bind_interface(linker: &mut Linker>) -> Result<(), Creat .map_err(|source| CreationError::LinkFunction { source, name: "user_settings_set_tooltip", + })? + .func_wrap("env", "settings_map_new", { + |mut caller: Caller<'_, Context>| { + let ctx = caller.data_mut(); + ctx.settings_maps.insert(SettingsMap::new()).data().as_ffi() + } + }) + .map_err(|source| CreationError::LinkFunction { + source, + name: "settings_map_new", + })? + .func_wrap("env", "settings_map_free", { + |mut caller: Caller<'_, Context>, settings_map: u64| { + caller + .data_mut() + .settings_maps + .remove(SettingsMapKey::from(KeyData::from_ffi(settings_map))) + .ok_or_else(|| format_err!("Invalid settings map handle: {settings_map}"))?; + Ok(()) + } + }) + .map_err(|source| CreationError::LinkFunction { + source, + name: "settings_map_free", + })? + .func_wrap("env", "settings_map_load", { + |mut caller: Caller<'_, Context>| { + let ctx = caller.data_mut(); + let settings_map = ctx.shared_data.settings_map.lock().unwrap().clone(); + ctx.settings_maps.insert(settings_map).data().as_ffi() + } + }) + .map_err(|source| CreationError::LinkFunction { + source, + name: "settings_map_load", + })? + .func_wrap("env", "settings_map_store", { + |mut caller: Caller<'_, Context>, settings_map: u64| { + let ctx = caller.data_mut(); + + let settings_map = ctx + .settings_maps + .get(SettingsMapKey::from(KeyData::from_ffi(settings_map))) + .ok_or_else(|| format_err!("Invalid settings map handle: {settings_map}"))? + .clone(); + + ctx.shared_data.set_settings_map(settings_map); + + Ok(()) + } + }) + .map_err(|source| CreationError::LinkFunction { + source, + name: "settings_map_store", + })? + .func_wrap("env", "settings_map_store_if_unchanged", { + |mut caller: Caller<'_, Context>, old_settings_map: u64, new_settings_map: u64| { + let ctx = caller.data_mut(); + + let old_settings_map = ctx + .settings_maps + .get(SettingsMapKey::from(KeyData::from_ffi(old_settings_map))) + .ok_or_else(|| { + format_err!("Invalid old settings map handle: {old_settings_map}") + })?; + + let new_settings_map = ctx + .settings_maps + .get(SettingsMapKey::from(KeyData::from_ffi(new_settings_map))) + .ok_or_else(|| { + format_err!("Invalid new settings map handle: {new_settings_map}") + })? + .clone(); + + let success = ctx + .shared_data + .set_settings_map_if_unchanged(old_settings_map, new_settings_map); + + Ok(success as u32) + } + }) + .map_err(|source| CreationError::LinkFunction { + source, + name: "settings_map_store_if_unchanged", + })? + .func_wrap("env", "settings_map_copy", { + |mut caller: Caller<'_, Context>, settings_map: u64| { + let ctx = caller.data_mut(); + + let settings_map = ctx + .settings_maps + .get(SettingsMapKey::from(KeyData::from_ffi(settings_map))) + .ok_or_else(|| format_err!("Invalid settings map handle: {settings_map}"))? + .clone(); + + Ok(ctx.settings_maps.insert(settings_map).data().as_ffi()) + } + }) + .map_err(|source| CreationError::LinkFunction { + source, + name: "settings_map_copy", + })? + .func_wrap("env", "settings_map_insert", { + |mut caller: Caller<'_, Context>, + settings_map: u64, + key_ptr: u32, + key_len: u32, + setting_value: u64| { + let (memory, context) = memory_and_context(&mut caller); + + let settings_map = context + .settings_maps + .get_mut(SettingsMapKey::from(KeyData::from_ffi(settings_map))) + .ok_or_else(|| format_err!("Invalid settings map handle: {settings_map}"))?; + + let setting_value = context + .setting_values + .get(SettingValueKey::from(KeyData::from_ffi(setting_value))) + .ok_or_else(|| format_err!("Invalid setting value handle: {setting_value}"))?; + + let key = get_str(memory, key_ptr, key_len)?; + + settings_map.insert(key.into(), setting_value.clone()); + + Ok(()) + } + }) + .map_err(|source| CreationError::LinkFunction { + source, + name: "settings_map_insert", + })? + .func_wrap("env", "settings_map_get", { + |mut caller: Caller<'_, Context>, settings_map: u64, key_ptr: u32, key_len: u32| { + let (memory, context) = memory_and_context(&mut caller); + + let settings_map = context + .settings_maps + .get(SettingsMapKey::from(KeyData::from_ffi(settings_map))) + .ok_or_else(|| format_err!("Invalid settings map handle: {settings_map}"))?; + + let key = get_str(memory, key_ptr, key_len)?; + + Ok(match settings_map.get(key) { + Some(value) => context.setting_values.insert(value.clone()).data().as_ffi(), + None => 0, + }) + } + }) + .map_err(|source| CreationError::LinkFunction { + source, + name: "settings_map_get", + })? + .func_wrap("env", "setting_value_new_bool", { + |mut caller: Caller<'_, Context>, value: u32| { + Ok(caller + .data_mut() + .setting_values + .insert(SettingValue::Bool(value != 0)) + .data() + .as_ffi()) + } + }) + .map_err(|source| CreationError::LinkFunction { + source, + name: "setting_value_new_bool", + })? + .func_wrap("env", "setting_value_free", { + |mut caller: Caller<'_, Context>, setting_value: u64| { + caller + .data_mut() + .setting_values + .remove(SettingValueKey::from(KeyData::from_ffi(setting_value))) + .ok_or_else(|| format_err!("Invalid setting value handle: {setting_value}"))?; + Ok(()) + } + }) + .map_err(|source| CreationError::LinkFunction { + source, + name: "setting_value_free", + })? + .func_wrap("env", "setting_value_get_bool", { + |mut caller: Caller<'_, Context>, setting_value: u64, value_ptr: u32| { + let (memory, context) = memory_and_context(&mut caller); + + let setting_value = context + .setting_values + .get(SettingValueKey::from(KeyData::from_ffi(setting_value))) + .ok_or_else(|| format_err!("Invalid setting value handle: {setting_value}"))?; + + let [out] = get_arr_mut(memory, value_ptr)?; + + let SettingValue::Bool(value) = setting_value; + *out = *value as u8; + Ok(1u32) + } + }) + .map_err(|source| CreationError::LinkFunction { + source, + name: "setting_value_get_bool", })?; Ok(()) } diff --git a/crates/livesplit-auto-splitting/src/settings.rs b/crates/livesplit-auto-splitting/src/settings.rs index 01e58a97..f7f83f9c 100644 --- a/crates/livesplit-auto-splitting/src/settings.rs +++ b/crates/livesplit-auto-splitting/src/settings.rs @@ -6,8 +6,8 @@ use std::{collections::HashMap, sync::Arc}; pub struct UserSetting { /// A unique identifier for this setting. This is not meant to be shown to /// the user and is only used to keep track of the setting. This key is used - /// to store and retrieve the value of the setting from the - /// [`SettingsStore`]. + /// to store and retrieve the value of the setting from the main + /// [`SettingsMap`]. pub key: Arc, /// The name of the setting that is shown to the user. pub description: Arc, @@ -32,7 +32,7 @@ pub enum UserSettingKind { /// A boolean setting. This could be visualized as a checkbox or a slider. Bool { /// The default value of the setting, if it's not available in the - /// settings store yet. + /// settings map yet. default_value: bool, }, } @@ -45,17 +45,17 @@ pub enum SettingValue { Bool(bool), } -/// Stores all the settings of an auto splitter. Currently this only stores +/// A key-value map that stores the settings of an auto splitter. It only stores /// values that are modified. So there may be settings that are registered as /// user settings, but because the user didn't modify them, they are not stored /// here yet. #[derive(Clone, Default)] -pub struct SettingsStore { +pub struct SettingsMap { values: Arc, SettingValue>>, } -impl SettingsStore { - /// Creates a new empty settings store. +impl SettingsMap { + /// Creates a new empty settings map. pub fn new() -> Self { Self::default() } @@ -63,19 +63,18 @@ impl SettingsStore { /// Sets a setting to the new value. If the key of the setting doesn't exist /// yet it will be stored as a new value. Otherwise the value will be /// updated. - pub fn set(&mut self, key: Arc, value: SettingValue) { + pub fn insert(&mut self, key: Arc, value: SettingValue) { Arc::make_mut(&mut self.values).insert(key, value); } /// Accesses the value of a setting by its key. While the setting may exist /// as part of the user settings, it may not have been stored into the - /// settings store yet, so it may not exist, despite being registered. + /// settings map yet, so it may not exist, despite being registered. pub fn get(&self, key: &str) -> Option<&SettingValue> { self.values.get(key) } - /// Iterates over all the setting keys and their values in the settings - /// store. + /// Iterates over all the setting keys and their values in the map. pub fn iter(&self) -> impl Iterator { self.values.iter().map(|(k, v)| (k.as_ref(), v)) } diff --git a/src/auto_splitting/mod.rs b/src/auto_splitting/mod.rs index 624efebd..ec613b50 100644 --- a/src/auto_splitting/mod.rs +++ b/src/auto_splitting/mod.rs @@ -40,6 +40,12 @@ //! pub struct ProcessId(u64); //! //! #[repr(transparent)] +//! pub struct SettingsMap(NonZeroU64); +//! +//! #[repr(transparent)] +//! pub struct SettingValue(NonZeroU64); +//! +//! #[repr(transparent)] //! pub struct TimerState(u32); //! //! impl TimerState { @@ -226,6 +232,63 @@ //! 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; //! } //! ``` //! @@ -252,7 +315,7 @@ use livesplit_auto_splitting::{ Config, CreationError, InterruptHandle, Runtime as ScriptRuntime, Timer as AutoSplitTimer, TimerState, }; -pub use livesplit_auto_splitting::{SettingValue, SettingsStore, UserSetting, UserSettingKind}; +pub use livesplit_auto_splitting::{SettingValue, SettingsMap, UserSetting, UserSettingKind}; use snafu::Snafu; use std::{fmt, fs, io, path::PathBuf, thread, time::Duration}; use tokio::{ @@ -635,7 +698,7 @@ async fn run( } Request::ReloadScript(ret) => { let mut config = Config::default(); - config.settings_store = Some(runtime.settings_store().clone()); + config.settings_map = Some(runtime.settings_map().clone()); match ScriptRuntime::new(&script_path, Timer(timer.clone()), config) { Ok(r) => { @@ -654,8 +717,8 @@ async fn run( log::info!(target: "Auto Splitter", "Getting the settings"); } Request::GetSettingValue(key, ret) => { - let store = &runtime.settings_store(); - let setting_value = store.get(key.as_str()); + let settings_map = runtime.settings_map(); + let setting_value = settings_map.get(key.as_str()); let user_setting_value = match runtime.user_settings().iter().find(|x| *x.key == key) { @@ -678,10 +741,10 @@ async fn run( } Request::SetSettingValue(key, value, ret) => { loop { - let old = runtime.settings_store(); + let old = runtime.settings_map(); let mut new = old.clone(); - new.set(key.clone(), value.clone()); - if runtime.set_settings_store_if_unchanged(old, new) { + new.insert(key.clone(), value.clone()); + if runtime.set_settings_map_if_unchanged(&old, new) { break; } }