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

Auto-splitting Updates #477

Merged
merged 7 commits into from
Jan 8, 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
4 changes: 4 additions & 0 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -290,12 +290,14 @@ jobs:
target: i586-pc-windows-msvc
os: windows-latest
cross: skip
auto_splitting: skip
install_target: true

- label: Windows i686
target: i686-pc-windows-msvc
os: windows-latest
cross: skip
auto_splitting: skip
install_target: true

- label: Windows x86_64
Expand All @@ -308,6 +310,7 @@ jobs:
toolchain: stable-i686-pc-windows-gnu
os: windows-latest
cross: skip
auto_splitting: skip
install_target: true

- label: Windows x86_64 gnu
Expand Down Expand Up @@ -639,6 +642,7 @@ jobs:
env:
TARGET: ${{ matrix.target }}
SKIP_CROSS: ${{ matrix.cross }}
SKIP_AUTO_SPLITTING: ${{ matrix.auto_splitting }}

- name: Prepare Release
if: startsWith(github.ref, 'refs/tags/') && matrix.release == ''
Expand Down
7 changes: 5 additions & 2 deletions .github/workflows/test.sh
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,15 @@ set -ex
main() {
local cargo=cross

# all features except those that don't easily work with cross such as font-loading
# all features except those that don't easily work with cross such as
# font-loading or auto-splitting.
local all_features="--features std,more-image-formats,image-shrinking,rendering,software-rendering,wasm-web,networking"

if [ "$SKIP_CROSS" = "skip" ]; then
cargo=cargo
all_features="--all-features"
if [ "$SKIP_AUTO_SPLITTING" -ne "skip" ]; then
all_features="--all-features"
fi
fi

if [ "$TARGET" = "wasm32-wasi" ]; then
Expand Down
6 changes: 6 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,11 @@ tiny-skia = { version = "0.6.0", default-features = false, features = [
# Networking
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, optional = true }
log = { version = "0.4.14", default-features = false, optional = true }

[target.'cfg(all(target_arch = "wasm32", target_os = "unknown"))'.dependencies]
# WebAssembly in the Web
js-sys = { version = "0.3.55", optional = true }
Expand Down Expand Up @@ -151,6 +156,7 @@ wasm-web = [
"web-sys",
]
networking = ["std", "splits-io-api"]
auto-splitting = ["std", "livesplit-auto-splitting", "crossbeam-channel", "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
19 changes: 19 additions & 0 deletions crates/livesplit-auto-splitting/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
[package]
name = "livesplit-auto-splitting"
version = "0.1.0"
authors = ["Christopher Serr <[email protected]>"]
edition = "2021"

[dependencies]
anyhow = { version = "1.0.45", default-features = false }
log = { version = "0.4.14", default-features = false }
proc-maps = { version = "0.2.0", default-features = false }
read-process-memory = { version = "0.1.4", default-features = false }
slotmap = { version = "1.0.2", default-features = false }
snafu = { version = "0.6.10", default-features = false, features = ["std"] }
sysinfo = { version = "0.22.4", default-features = false, features = ["multithread"] }
time = { version = "0.3.3", default-features = false }
wasmtime = { version = "0.33.0", default-features = false, features = [
"cranelift",
"parallel-compilation",
] }
110 changes: 110 additions & 0 deletions crates/livesplit-auto-splitting/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
# <img src="https://raw.githubusercontent.com/LiveSplit/LiveSplit/master/LiveSplit/Resources/Icon.png" alt="LiveSplit" height="42" width="45" align="top"/> livesplit-auto-splitting

livesplit-auto-splitting is a library that provides a runtime for running
auto splitters that can control a speedrun timer. These auto splitters are
provided as WebAssembly modules.

## Requirements for the Auto Splitters

The auto splitters must provide an `update` function with the following
signature:

```rust
#[no_mangle]
pub extern "C" fn update() {}
```

This function is called periodically by the runtime at the configured tick
rate. The tick rate is 120 Hz by default, but can be changed by the auto
splitter.

In addition the WebAssembly module is expected to export a memory called
`memory`.

## API exposed to the Auto Splitters

The following functions are provided to the auto splitters in the module
`env`:

```rust
#[repr(transparent)]
pub struct Address(pub u64);

#[repr(transparent)]
pub struct NonZeroAddress(pub NonZeroU64);

#[repr(transparent)]
pub struct ProcessId(NonZeroU64);

#[repr(transparent)]
pub struct TimerState(u32);

impl TimerState {
/// The timer is not running.
pub const NOT_RUNNING: Self = Self(0);
/// The timer is running.
pub const RUNNING: Self = Self(1);
/// The timer started but got paused. This is separate from the game
/// time being paused. Game time may even always be paused.
pub const PAUSED: Self = Self(2);
/// The timer has ended, but didn't get reset yet.
pub const ENDED: Self = Self(3);
}

extern "C" {
/// Gets the state that the timer currently is in.
pub fn timer_get_state() -> TimerState;

/// Starts the timer.
pub fn timer_start();
/// Splits the current segment.
pub fn timer_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.
pub fn timer_set_variable(
key_ptr: *const u8,
key_len: usize,
value_ptr: *const u8,
value_len: usize,
);

/// Sets the game time.
pub fn timer_set_game_time(secs: i64, nanos: i32);
/// Pauses the game time. This does not pause the timer, only the
/// automatic flow of time for the game time.
pub fn timer_pause_game_time();
/// Resumes the game time. This does not resume the timer, only the
/// automatic flow of time for the game time.
pub fn timer_resume_game_time();

/// Attaches to a process based on its name.
pub fn process_attach(name_ptr: *const u8, name_len: usize) -> Option<ProcessId>;
/// Detaches from a process.
pub fn process_detach(process: ProcessId);
/// Checks whether is a process is still open. You should detach from a
/// process and stop using it if this returns `false`.
pub fn process_is_open(process: ProcessId) -> bool;
/// Reads memory from a process at the address given. This will write
/// the memory to the buffer given. Returns `false` if this fails.
pub fn process_read(
process: ProcessId,
address: Address,
buf_ptr: *mut u8,
buf_len: usize,
) -> bool;
/// Gets the address of a module in a process.
pub fn process_get_module_address(
process: ProcessId,
name_ptr: *const u8,
name_len: usize,
) -> Option<NonZeroAddress>;

/// 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.
pub fn runtime_print_message(text_ptr: *const u8, text_len: usize);
}
```
126 changes: 126 additions & 0 deletions crates/livesplit-auto-splitting/src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
//! livesplit-auto-splitting is a library that provides a runtime for running
//! auto splitters that can control a speedrun timer. These auto splitters are
//! provided as WebAssembly modules.
//!
//! # Requirements for the Auto Splitters
//!
//! The auto splitters must provide an `update` function with the following
//! signature:
//!
//! ```rust
//! #[no_mangle]
//! pub extern "C" fn update() {}
//! ```
//!
//! This function is called periodically by the runtime at the configured tick
//! rate. The tick rate is 120 Hz by default, but can be changed by the auto
//! splitter.
//!
//! In addition the WebAssembly module is expected to export a memory called
//! `memory`.
//!
//! # API exposed to the Auto Splitters
//!
//! The following functions are provided to the auto splitters in the module
//! `env`:
//!
//! ```rust
//! #[repr(transparent)]
//! pub struct Address(pub u64);
//!
//! #[repr(transparent)]
//! pub struct NonZeroAddress(pub NonZeroU64);
//!
//! #[repr(transparent)]
//! pub struct ProcessId(NonZeroU64);
//!
//! #[repr(transparent)]
//! pub struct TimerState(u32);
//!
//! impl TimerState {
//! /// The timer is not running.
//! pub const NOT_RUNNING: Self = Self(0);
//! /// The timer is running.
//! pub const RUNNING: Self = Self(1);
//! /// The timer started but got paused. This is separate from the game
//! /// time being paused. Game time may even always be paused.
//! pub const PAUSED: Self = Self(2);
//! /// The timer has ended, but didn't get reset yet.
//! pub const ENDED: Self = Self(3);
//! }
//!
//! extern "C" {
//! /// Gets the state that the timer currently is in.
//! pub fn timer_get_state() -> TimerState;
//!
//! /// Starts the timer.
//! pub fn timer_start();
//! /// Splits the current segment.
//! pub fn timer_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.
//! pub fn timer_set_variable(
//! key_ptr: *const u8,
//! key_len: usize,
//! value_ptr: *const u8,
//! value_len: usize,
//! );
//!
//! /// Sets the game time.
//! pub fn timer_set_game_time(secs: i64, nanos: i32);
//! /// Pauses the game time. This does not pause the timer, only the
//! /// automatic flow of time for the game time.
//! pub fn timer_pause_game_time();
//! /// Resumes the game time. This does not resume the timer, only the
//! /// automatic flow of time for the game time.
//! pub fn timer_resume_game_time();
//!
//! /// Attaches to a process based on its name.
//! pub fn process_attach(name_ptr: *const u8, name_len: usize) -> Option<ProcessId>;
//! /// Detaches from a process.
//! pub fn process_detach(process: ProcessId);
//! /// Checks whether is a process is still open. You should detach from a
//! /// process and stop using it if this returns `false`.
//! pub fn process_is_open(process: ProcessId) -> bool;
//! /// Reads memory from a process at the address given. This will write
//! /// the memory to the buffer given. Returns `false` if this fails.
//! pub fn process_read(
//! process: ProcessId,
//! address: Address,
//! buf_ptr: *mut u8,
//! buf_len: usize,
//! ) -> bool;
//! /// Gets the address of a module in a process.
//! pub fn process_get_module_address(
//! process: ProcessId,
//! name_ptr: *const u8,
//! name_len: usize,
//! ) -> Option<NonZeroAddress>;
//!
//! /// 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.
//! pub fn runtime_print_message(text_ptr: *const u8, text_len: usize);
//! }
//! ```

#![warn(
clippy::complexity,
clippy::correctness,
clippy::perf,
clippy::style,
clippy::missing_const_for_fn,
missing_docs,
rust_2018_idioms
)]

mod process;
mod runtime;
mod timer;

pub use runtime::{CreationError, RunError, Runtime};
pub use timer::{Timer, TimerState};
pub use wasmtime::InterruptHandle;
Loading