Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Introduce a Watchdog in the Auto Splitting Runtime #528

Merged
merged 1 commit into from
Apr 24, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 6 additions & 2 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,11 @@ splits-io-api = { version = "0.2.0", optional = true }

# Auto Splitting
livesplit-auto-splitting = { path = "crates/livesplit-auto-splitting", version = "0.1.0", optional = true }
crossbeam-channel = { version = "0.5.1", default-features = false, features = ["std"], optional = true }
tokio = { version = "1.17.0", default-features = false, features = [
"rt",
"sync",
"time",
], optional = true }
log = { version = "0.4.14", default-features = false, optional = true }

[target.'cfg(all(target_arch = "wasm32", target_os = "unknown"))'.dependencies]
Expand Down Expand Up @@ -156,7 +160,7 @@ wasm-web = [
"web-sys",
]
networking = ["std", "splits-io-api"]
auto-splitting = ["std", "livesplit-auto-splitting", "crossbeam-channel", "log"]
auto-splitting = ["std", "livesplit-auto-splitting", "tokio", "log"]

# FIXME: Some targets don't have atomics, but we can't test for this properly
# yet. So there's a feature you explicitly have to opt into to deactivate the
Expand Down
11 changes: 5 additions & 6 deletions capi/src/auto_splitting_runtime.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,7 @@

use super::str;
use crate::shared_timer::OwnedSharedTimer;
use std::os::raw::c_char;
use std::path::PathBuf;
use std::{os::raw::c_char, path::PathBuf};

#[cfg(feature = "auto-splitting")]
use livesplit_core::auto_splitting::Runtime as AutoSplittingRuntime;
Expand All @@ -23,11 +22,11 @@ impl AutoSplittingRuntime {
Self
}

pub fn unload_script(&self) -> Result<(), ()> {
pub fn unload_script_blocking(&self) -> Result<(), ()> {
Err(())
}

pub fn load_script(&self, _: PathBuf) -> Result<(), ()> {
pub fn load_script_blocking(&self, _: PathBuf) -> Result<(), ()> {
Err(())
}
}
Expand All @@ -53,7 +52,7 @@ pub unsafe extern "C" fn AutoSplittingRuntime_load_script(
) -> bool {
let path = str(path);
if !path.is_empty() {
this.load_script(PathBuf::from(path)).is_ok()
this.load_script_blocking(PathBuf::from(path)).is_ok()
} else {
false
}
Expand All @@ -62,7 +61,7 @@ pub unsafe extern "C" fn AutoSplittingRuntime_load_script(
/// Attempts to unload the auto splitter. Returns true if successful.
#[no_mangle]
pub extern "C" fn AutoSplittingRuntime_unload_script(this: &AutoSplittingRuntime) -> bool {
this.unload_script().is_ok()
this.unload_script_blocking().is_ok()
}

/// drop
Expand Down
2 changes: 1 addition & 1 deletion capi/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,9 @@ use std::{
};

pub mod analysis;
pub mod auto_splitting_runtime;
pub mod atomic_date_time;
pub mod attempt;
pub mod auto_splitting_runtime;
pub mod blank_space_component;
pub mod blank_space_component_state;
pub mod component;
Expand Down
2 changes: 1 addition & 1 deletion crates/livesplit-auto-splitting/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ slotmap = { version = "1.0.2", default-features = false }
snafu = "0.7.0"
sysinfo = { version = "0.23.0", default-features = false, features = ["multithread"] }
time = { version = "0.3.3", default-features = false }
wasmtime = { version = "0.35.1", default-features = false, features = [
wasmtime = { version = "0.36.0", default-features = false, features = [
"cranelift",
"parallel-compilation",
] }
3 changes: 1 addition & 2 deletions crates/livesplit-auto-splitting/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,5 @@ mod process;
mod runtime;
mod timer;

pub use runtime::{CreationError, RunError, Runtime};
pub use runtime::{CreationError, InterruptHandle, RunError, Runtime};
pub use timer::{Timer, TimerState};
pub use wasmtime::InterruptHandle;
55 changes: 28 additions & 27 deletions crates/livesplit-auto-splitting/src/runtime.rs
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
use crate::{process::Process, timer::Timer, InterruptHandle};
use crate::{process::Process, timer::Timer};

use log::info;
use slotmap::{Key, KeyData, SlotMap};
use snafu::{ResultExt, Snafu};
use std::{
path::Path,
result, str, thread,
result, str,
time::{Duration, Instant},
};
use sysinfo::{ProcessRefreshKind, RefreshKind, System, SystemExt};
Expand Down Expand Up @@ -40,14 +40,14 @@ pub enum CreationError {
/// The underlying error.
source: anyhow::Error,
},
/// The WebAssembly module has no exported function named `update`, which is
/// The WebAssembly module has no exported function called `update`, which is
/// a required function.
MissingUpdateFunction {
/// The underlying error.
source: anyhow::Error,
},
/// The WebAssembly module has no exported function memory called `memory`,
/// which is a requirement.
/// The WebAssembly module has no exported memory called `memory`, which is
/// a requirement.
MissingMemory,
}

Expand All @@ -74,6 +74,16 @@ pub struct Context<T: Timer> {
process_list: ProcessList,
}

/// A threadsafe handle used to interrupt the execution of the script.
pub struct InterruptHandle(Engine);

impl InterruptHandle {
/// Interrupts the execution.
pub fn interrupt(&self) {
self.0.increment_epoch();
}
}

pub struct ProcessList {
system: System,
last_check: Instant,
Expand Down Expand Up @@ -115,20 +125,22 @@ impl ProcessList {
pub struct Runtime<T: Timer> {
store: Store<Context<T>>,
update: TypedFunc<(), ()>,
prev_time: Instant,
engine: Engine,
}

impl<T: Timer> Runtime<T> {
/// Creates a new runtime with the given path to the WebAssembly module and
/// the timer that the module then controls.
pub fn new<P: AsRef<Path>>(path: P, timer: T) -> Result<Self, CreationError> {
pub fn new(path: &Path, timer: T) -> Result<Self, CreationError> {
let engine = Engine::new(
Config::new()
.cranelift_opt_level(OptLevel::Speed)
.interruptable(true),
.epoch_interruption(true),
)
.context(EngineCreation)?;

let module = Module::from_file(&engine, path).context(ModuleLoading)?;

let mut store = Store::new(
&engine,
Context {
Expand All @@ -140,7 +152,8 @@ impl<T: Timer> Runtime<T> {
},
);

let module = Module::from_file(&engine, path).context(ModuleLoading)?;
store.set_epoch_deadline(1);

let mut linker = Linker::new(&engine);
bind_interface(&mut linker)?;
let instance = linker
Expand All @@ -158,37 +171,25 @@ impl<T: Timer> Runtime<T> {
}

Ok(Self {
engine,
store,
update,
prev_time: Instant::now(),
})
}

/// Accesses an interrupt handle that allows you to interrupt the ongoing
/// execution of the WebAssembly module. A WebAssembly module may
/// accidentally or maliciously loop forever, which is why this is needed.
pub fn interrupt_handle(&self) -> InterruptHandle {
self.store
.interrupt_handle()
.expect("We configured the runtime to produce an interrupt handle.")
InterruptHandle(self.engine.clone())
}

/// Runs the exported `update` function of the WebAssembly module a single
/// time. If the module has not been configured yet, this will also call the
/// optional `configure` function beforehand.
pub fn step(&mut self) -> Result<(), RunError> {
self.update.call(&mut self.store, ()).context(RunUpdate)
}

/// Sleeps until the next tick based on the current tick rate. The auto
/// time and returns the duration to wait until the next execution. The auto
/// splitter can change this tick rate. It is 120Hz by default.
pub fn sleep(&mut self) {
let target = self.store.data().tick_rate;
let delta = self.prev_time.elapsed();
if delta < target {
thread::sleep(target - delta);
}
self.prev_time = Instant::now();
pub fn step(&mut self) -> Result<Duration, RunError> {
self.update.call(&mut self.store, ()).context(RunUpdate)?;
Ok(self.store.data().tick_rate)
}
}

Expand Down
Loading