Skip to content

Commit

Permalink
Add requests for auto splitter settings management (#713)
Browse files Browse the repository at this point in the history
This patch adds 4 requests to the auto-splitting feature so that the library users are able to manage the auto splitter settings:
- GetSettings: Gets all of the user settings that are meant to be shown to the runner
- GetSettingsValue(key: String): Gets the SettingValue of a specific setting
- SetSettingsValue(key: String, value: SettingValue): Sets the SettingValue of a specific setting
- ReloadScript: Helper request that will reload the current script while keeping the same SettingsStore

This adds 3 new possible errors to tell the user what could have went wrong while executing the requests.

It also re export some of the useful types for settings related things.

Lastly, there's a new accessor in the runtime to get the SettingsStore as mutable and UserSetting / UserSettingKind are now clonable so that we can pass them along with the requests.
  • Loading branch information
Refragg authored Aug 25, 2023
1 parent ea833ad commit 488df3d
Show file tree
Hide file tree
Showing 3 changed files with 198 additions and 2 deletions.
5 changes: 5 additions & 0 deletions crates/livesplit-auto-splitting/src/runtime.rs
Original file line number Diff line number Diff line change
Expand Up @@ -320,6 +320,11 @@ impl<T: Timer> Runtime<T> {
&self.store.data().settings_store
}

/// Accesses the currently stored settings as mutable.
pub fn settings_store_mut(&mut self) -> &mut SettingsStore {
&mut self.store.data_mut().settings_store
}

/// Accesses all the settings that are meant to be shown to and modified by
/// the user.
pub fn user_settings(&self) -> &[UserSetting] {
Expand Down
2 changes: 2 additions & 0 deletions crates/livesplit-auto-splitting/src/settings.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ use std::collections::HashMap;

/// A setting that is meant to be shown to and modified by the user.
#[non_exhaustive]
#[derive(Clone)]
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
Expand All @@ -18,6 +19,7 @@ pub struct UserSetting {
}

/// The type of a [`UserSetting`] and additional information about it.
#[derive(Clone)]
pub enum UserSettingKind {
/// A title that is shown to the user. It doesn't by itself store a value
/// and is instead used to group settings together.
Expand Down
193 changes: 191 additions & 2 deletions src/auto_splitting/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -197,6 +197,7 @@ use livesplit_auto_splitting::{
Config, CreationError, InterruptHandle, Runtime as ScriptRuntime, Timer as AutoSplitTimer,
TimerState,
};
pub use livesplit_auto_splitting::{SettingValue, SettingsStore, UserSetting, UserSettingKind};
use snafu::Snafu;
use std::{fmt, fs, io, path::PathBuf, thread, time::Duration};
use tokio::{
Expand All @@ -215,11 +216,17 @@ pub enum Error {
/// The underlying error.
source: CreationError,
},
/// Tried reloading the auto splitter when no auto splitter is loaded
ImpossibleReload,
/// Failed reading the auto splitter file.
ReadFileFailed {
/// The underlying error.
source: io::Error,
},
/// Failed reading the auto splitter settings.
SettingsLoadFailed,
/// The asked setting was not found.
SettingNotFound,
}

/// An auto splitter runtime that allows using an auto splitter provided as a
Expand Down Expand Up @@ -324,11 +331,117 @@ impl Runtime {
.unwrap()
.block_on(self.unload_script())
}

/// Attempts to reload the currently loaded auto splitter.
/// This call will block until the auto splitter has either reloaded successfully
/// or failed to reload.
pub async fn reload_script(&self) -> Result<(), Error> {
let (sender, receiver) = oneshot::channel();
self.sender
.send(Request::ReloadScript(sender))
.map_err(|_| Error::ThreadStopped)?;

receiver.await.map_err(|_| Error::ThreadStopped)??;

Ok(())
}

/// Attempts to reload the currently loaded auto splitter.
/// This call will block until the auto splitter has either reloaded successfully
/// or failed to reload.
pub fn reload_script_blocking(&self) -> Result<(), Error> {
runtime::Builder::new_current_thread()
.enable_time()
.build()
.unwrap()
.block_on(self.reload_script())
}

/// Get the custom auto splitter settings
pub async fn get_settings(&self) -> Result<Vec<UserSetting>, Error> {
let (sender, receiver) = oneshot::channel();
self.sender
.send(Request::GetSettings(sender))
.map_err(|_| Error::ThreadStopped)?;

let result = receiver.await;

match result {
Ok(settings) => match settings {
Some(settings) => Ok(settings),
None => Err(Error::SettingsLoadFailed),
},
Err(_) => Err(Error::ThreadStopped),
}
}

/// Get the custom auto splitter settings
pub fn get_settings_blocking(&self) -> Result<Vec<UserSetting>, Error> {
runtime::Builder::new_current_thread()
.enable_time()
.build()
.unwrap()
.block_on(self.get_settings())
}

/// Get the value for a custom auto splitter setting
pub async fn get_setting_value(&self, key: String) -> Result<SettingValue, Error> {
let (sender, receiver) = oneshot::channel();
self.sender
.send(Request::GetSettingValue(key, sender))
.map_err(|_| Error::ThreadStopped)?;

let result = receiver.await;

match result {
Ok(setting_value) => match setting_value {
Some(setting_value) => Ok(setting_value),
None => Err(Error::SettingNotFound),
},
Err(_) => Err(Error::ThreadStopped),
}
}

/// Get the value for a custom auto splitter setting
pub fn get_setting_value_blocking(&self, key: String) -> Result<SettingValue, Error> {
runtime::Builder::new_current_thread()
.enable_time()
.build()
.unwrap()
.block_on(self.get_setting_value(key))
}

/// Set the value for a custom auto splitter setting
pub async fn set_setting_value(&self, key: String, value: SettingValue) -> Result<(), Error> {
let (sender, receiver) = oneshot::channel();
self.sender
.send(Request::SetSettingValue(key, value, sender))
.map_err(|_| Error::ThreadStopped)?;

receiver.await.map_err(|_| Error::ThreadStopped)
}

/// Set the value for a custom auto splitter setting
pub fn set_setting_value_blocking(
&self,
key: String,
value: SettingValue,
) -> Result<(), Error> {
runtime::Builder::new_current_thread()
.enable_time()
.build()
.unwrap()
.block_on(self.set_setting_value(key, value))
}
}

enum Request {
LoadScript(Vec<u8>, oneshot::Sender<Result<(), Error>>),
UnloadScript(oneshot::Sender<()>),
ReloadScript(oneshot::Sender<Result<(), Error>>),
GetSettings(oneshot::Sender<Option<Vec<UserSetting>>>),
GetSettingValue(String, oneshot::Sender<Option<SettingValue>>),
SetSettingValue(String, SettingValue, oneshot::Sender<()>),
}

// This newtype is required because [`SharedTimer`](crate::timing::SharedTimer)
Expand Down Expand Up @@ -396,12 +509,15 @@ async fn run(
interrupt_sender.send(None).ok();
timeout_sender.send(None).ok();

let mut script_path;

let mut runtime = loop {
match receiver.recv().await {
Some(Request::LoadScript(script, ret)) => {
match ScriptRuntime::new(&script, Timer(timer.clone()), Config::default()) {
Ok(r) => {
ret.send(Ok(())).ok();
script_path = script;
break r;
}
Err(source) => {
Expand All @@ -413,6 +529,22 @@ async fn run(
log::warn!(target: "Auto Splitter", "Attempted to unload already unloaded script");
ret.send(()).ok();
}
Some(Request::ReloadScript(ret)) => {
log::warn!(target: "Auto Splitter", "Attempted to reload a non existing script");
ret.send(Err(Error::ImpossibleReload)).ok();
}
Some(Request::GetSettings(ret)) => {
log::warn!(target: "Auto Splitter", "Attempted to get the settings when no script is loaded");
ret.send(None).ok();
}
Some(Request::GetSettingValue(_, ret)) => {
log::warn!(target: "Auto Splitter", "Attempted to get a setting value when no script is loaded");
ret.send(None).ok();
}
Some(Request::SetSettingValue(_, _, ret)) => {
log::warn!(target: "Auto Splitter", "Attempted to set a setting value when no script is loaded");
ret.send(()).ok();
}
None => {
return;
}
Expand All @@ -432,11 +564,12 @@ async fn run(
Ok(r) => {
ret.send(Ok(())).ok();
runtime = r;
log::info!(target: "Auto Splitter", "Reloaded script");
script_path = script;
log::info!(target: "Auto Splitter", "Loaded new script");
}
Err(source) => {
ret.send(Err(Error::LoadFailed { source })).ok();
log::info!(target: "Auto Splitter", "Failed to load");
log::info!(target: "Auto Splitter", "Failed to load the new script");
}
}
}
Expand All @@ -445,6 +578,62 @@ async fn run(
log::info!(target: "Auto Splitter", "Unloaded script");
continue 'back_to_not_having_a_runtime;
}
Request::ReloadScript(ret) => {

Check failure on line 581 in src/auto_splitting/mod.rs

View workflow job for this annotation

GitHub Actions / Check formatting

Diff in /home/runner/work/livesplit-core/livesplit-core/src/auto_splitting/mod.rs
let mut config = Config::default();
config.settings_store = Some(runtime.settings_store().clone());

match ScriptRuntime::new(
&script_path,
Timer(timer.clone()),
config,
) {
Ok(r) => {
ret.send(Ok(())).ok();
runtime = r;
log::info!(target: "Auto Splitter", "Reloaded script");
}
Err(source) => {
ret.send(Err(Error::LoadFailed { source })).ok();
log::info!(target: "Auto Splitter", "Failed to reload the script");
}
}
}
Request::GetSettings(ret) => {
ret.send(Some(runtime.user_settings().to_vec())).ok();
log::info!(target: "Auto Splitter", "Getting the settings");
}
Request::GetSettingValue(key, ret) => {
let setting_value = runtime.settings_store().get(key.as_str());

let user_setting_value = match runtime
.user_settings()
.iter()
.find(|x| x.key == key.clone().into_boxed_str())
{
Some(user_setting) => match user_setting.kind {
UserSettingKind::Bool { default_value } => {
Some(SettingValue::Bool(default_value))
}
UserSettingKind::Title { heading_level: _ } => None,
},
None => None,
};

if setting_value.is_some() {
ret.send(setting_value.cloned()).ok();
} else {
ret.send(user_setting_value).ok();
}

log::info!(target: "Auto Splitter", "Getting value for {}", key);
}
Request::SetSettingValue(key, value, ret) => {
runtime
.settings_store_mut()
.set(key.clone().into_boxed_str(), value);
ret.send(()).ok();
log::info!(target: "Auto Splitter", "Setting value for {}", key);
}
},
Ok(None) => return,
Err(_) => match runtime.update() {
Expand Down

0 comments on commit 488df3d

Please sign in to comment.