Skip to content

Commit

Permalink
Support maps and strings as setting values (#75)
Browse files Browse the repository at this point in the history
This allows for more complex settings to be stored. There is no GUI
equivalent for those yet though.
  • Loading branch information
CryZe authored Nov 1, 2023
1 parent b129bbf commit 224d7ef
Show file tree
Hide file tree
Showing 2 changed files with 151 additions and 36 deletions.
89 changes: 77 additions & 12 deletions src/runtime/settings/value.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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>) -> 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<Map> {
// 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<bool> {
Expand All @@ -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<alloc::string::String> {
// 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<bool> 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()) })
}
}
98 changes: 74 additions & 24 deletions src/runtime/sys.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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<Process>;
/// Attaches to a process based on its process id.
pub fn process_attach_by_pid(pid: ProcessId) -> Option<Process>;
/// 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,
Expand All @@ -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<NonZeroAddress>;
/// 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<NonZeroU64>;

/// 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<NonZeroU64>;
/// Gets the start address of a memory range by its index.
Expand All @@ -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.
Expand All @@ -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,
Expand All @@ -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,
Expand All @@ -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,
Expand Down Expand Up @@ -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,
Expand All @@ -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<SettingValue>;

/// 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;
}

0 comments on commit 224d7ef

Please sign in to comment.