pub const HOST_LIBRARY: &str = "host";
pub const FOREIGN_CALL: &str = "foreign_call";
pub const SLEEP: &str = "sleep";
pub const SLEEP_MS: &str = "sleep_ms";
pub const RANDOM: &str = "random";
pub const RANDOM_BELOW: &str = "random_below";
pub const RANDOM_SEED: &str = "random_seed";
pub const CLOCK: &str = "clock";
pub const LOCAL_OFFSET: &str = "local_offset";
pub const SIGNAL_POLL: &str = "signal_poll";
pub const SIGNAL_LISTEN: &str = "signal_listen";
pub const LISTEN_EVERY: i64 = 0;
pub const LISTEN_FRAME: i64 = 1;
pub const LISTEN_CLOCK: i64 = 2;
pub const CLOCK_WEEKDAYS: i64 = 10_000;
pub const SIGNAL_WATCH: &str = "signal_watch";
pub const SYSTEM_VALUE: &str = "system_value";
pub const SET_SYSTEM_VALUE: &str = "set_system_value";
pub const BATTERY: &str = "battery";
pub const CHARGING: &str = "charging";
pub const ONLINE: &str = "online";
pub const DARK_MODE: &str = "dark mode";
pub const CLIPBOARD_COUNT: &str = "clipboard count";
pub const MOUSE_X: &str = "mouse_x";
pub const MOUSE_Y: &str = "mouse_y";
pub const MOUSE_DOWN: &str = "mouse_down";
pub const MOUSE_WHEEL: &str = "mouse_wheel";
pub const PIXELS_PER_WHEEL_STEP: f64 = 100.0;
pub const KEY_DOWN: &str = "key_down";
pub const HELD_KEY: &str = "held_key";
pub const KEY_CODE_HELD: &str = "key code held";
pub const KEY_HELD: &str = "key_held";
pub const WINDOW_OPEN: &str = "window_open";
pub const TIME_OF_DAY: &str = "time of day";
pub const VIEW_WIDTH: &str = "view_width";
pub const VIEW_HEIGHT: &str = "view_height";
pub const GPU_DRAWING: &str = "gpu_drawing";
pub const WINDOW_WIDTH: &str = "window_width";
pub const WINDOW_HEIGHT: &str = "window_height";
pub const DISPLAY_WIDTH: &str = "display_width";
pub const DISPLAY_HEIGHT: &str = "display_height";
pub const DISPLAY_DPI: &str = "display_dpi";
pub const KEY_PRESSED: &str = "key_pressed";
pub const GPU_PAINTS: &str = "gpu paints";
pub const ESCAPE_KEPT: &str = "escape kept";
pub const CLICK_PRESSED: &str = "click_pressed";
pub const CLICK_X: &str = "click_x";
pub const CLICK_Y: &str = "click_y";
pub const SYSTEM_VALUES: [(&str, bool); 19] = [
(BATTERY, false),
(CHARGING, true),
(ONLINE, true),
(DARK_MODE, true),
(CLIPBOARD_COUNT, false),
(MOUSE_X, false),
(MOUSE_Y, false),
(MOUSE_DOWN, true),
(MOUSE_WHEEL, false),
(KEY_DOWN, true),
(WINDOW_OPEN, true),
(VIEW_WIDTH, false),
(VIEW_HEIGHT, false),
(GPU_DRAWING, true),
(WINDOW_WIDTH, false),
(WINDOW_HEIGHT, false),
(DISPLAY_WIDTH, false),
(DISPLAY_HEIGHT, false),
(DISPLAY_DPI, false),
];
pub const CLIPBOARD: &str = "clipboard";
pub const CLIPBOARD_TEXT: &str = "clipboard_text";
pub const PAGE_PATH: &str = "page_path";
pub const NOTIFY: &str = "notify";
pub const GPU_COMPUTE: &str = "gpu_compute";
pub const GPU_RENDER: &str = "gpu_render";
pub const TEXT_COVERAGE: &str = "text_coverage";
pub const GPU_COMPUTE_LINEAR: &str = "gpu_compute_linear";
pub const GPU_MAP_LINEAR: &str = "gpu_map_linear";
pub const GPU_REDUCE_LINEAR: &str = "gpu_reduce_linear";
pub const HANDLER_PREFIX: &str = "on·";
pub const FILE_HANDLER_PREFIX: &str = "on·file·";
pub const EXIT: &str = "exit";
pub const INTERRUPT_HANDLER: &str = "on·interrupt";
pub const EXIT_HANDLER: &str = "on·exit";
pub const TIMER_HANDLER_PREFIX: &str = "on·every·";
pub const FETCH_HANDLER_PREFIX: &str = "on·fetch·";
pub const SHARED_HANDLER: &str = "on·shared";
pub const BASIC_HOST_WORDS: [&str; 13] = [
SLEEP_MS,
RANDOM,
RANDOM_BELOW,
RANDOM_SEED,
CLOCK,
LOCAL_OFFSET,
SIGNAL_POLL,
SIGNAL_LISTEN,
SIGNAL_WATCH,
SYSTEM_VALUE,
SET_SYSTEM_VALUE,
KEY_HELD,
EXIT,
];
#[cfg(feature = "engine")]
pub use linking::*;
#[cfg(feature = "engine")]
mod linking {
use super::*;
use crate::system_signals;
use std::time::Duration;
use wasmtime::{Caller, Func, Linker, Result};
pub fn link_host_words<T: 'static>(linker: &mut Linker<T>) -> Result<()> {
system_signals::forget_timers(); RANDOM_STATE.set(0); linker.func_wrap(HOST_LIBRARY, SLEEP_MS, |mut caller: Caller<'_, T>, milliseconds: i64| {
system_signals::end_at_first_frame()?;
system_signals::sleep_with_handlers(
&mut caller,
Duration::from_millis(milliseconds.max(0) as u64),
exported,
)
})?;
linker.func_wrap(HOST_LIBRARY, RANDOM, || (next_random() >> 11) as f64 / (1u64 << 53) as f64)?;
linker.func_wrap(HOST_LIBRARY, RANDOM_BELOW, |bound: i64| {
if bound <= 0 {
0
} else {
(next_random() % bound as u64) as i64
}
})?;
linker.func_wrap(HOST_LIBRARY, RANDOM_SEED, |seed: i64| RANDOM_STATE.set(seeded_state(seed as u64)))?;
linker.func_wrap(HOST_LIBRARY, CLOCK, milliseconds_since_epoch)?;
linker.func_wrap(HOST_LIBRARY, LOCAL_OFFSET, system_signals::local_offset_at)?;
linker.func_wrap(HOST_LIBRARY, SIGNAL_POLL, |mut caller: Caller<'_, T>| {
system_signals::run_due_handlers(&mut caller, exported)
})?;
linker.func_wrap(HOST_LIBRARY, SIGNAL_LISTEN, system_signals::listen)?;
linker.func_wrap(
HOST_LIBRARY,
SIGNAL_WATCH,
|mut caller: Caller<'_, T>, id: i64, path: i32| -> Result<()> {
let path = c_string(&mut caller, path)?;
system_signals::watch_file(id, path);
Ok(())
},
)?;
linker.func_wrap(HOST_LIBRARY, SYSTEM_VALUE, |mut caller: Caller<'_, T>, name: i32| -> Result<i64> {
let name = c_string(&mut caller, name)?;
crate::system_values::read(&name).map_err(wasmtime::Error::msg)
})?;
linker.func_wrap(
HOST_LIBRARY,
SET_SYSTEM_VALUE,
|mut caller: Caller<'_, T>, name: i32, value: i64| -> Result<()> {
let name = c_string(&mut caller, name)?;
crate::system_values::write(&name, value).map_err(wasmtime::Error::msg)
},
)?;
linker.func_wrap(HOST_LIBRARY, KEY_HELD, |code: i64| crate::system_values::key_held(code) as i64)?;
linker.func_wrap(HOST_LIBRARY, EXIT, |code: i64| -> Result<()> {
Err(wasmtime::Error::new(system_signals::ExitRequest(code as i32)))
})?;
Ok(())
}
fn c_string<T>(caller: &mut Caller<'_, T>, pointer: i32) -> Result<String> {
let memory = caller
.get_export("memory")
.and_then(|export| export.into_memory())
.ok_or_else(|| wasmtime::Error::msg("no memory export"))?;
let bytes = memory.data(&*caller);
let start = (pointer as usize).min(bytes.len());
let end = bytes[start..].iter().position(|byte| *byte == 0).map_or(bytes.len(), |length| start + length);
Ok(String::from_utf8_lossy(&bytes[start..end]).into_owned())
}
fn exported<T>(caller: &mut Caller<'_, T>, name: &str) -> Option<Func> {
caller.get_export(name).and_then(|export| export.into_func())
}
thread_local! {
static RANDOM_STATE: std::cell::Cell<u64> = const { std::cell::Cell::new(0) };
}
static UNSEEDED_RUNS: std::sync::atomic::AtomicU64 = std::sync::atomic::AtomicU64::new(0);
fn seeded_state(seed: u64) -> u64 {
seed.wrapping_mul(0x9E37_79B9_7F4A_7C15) | 1
}
fn next_random() -> u64 {
let mut x = RANDOM_STATE.get();
if x == 0 {
let run = UNSEEDED_RUNS.fetch_add(1, std::sync::atomic::Ordering::Relaxed);
x = seeded_state(milliseconds_since_epoch() as u64 ^ run.wrapping_mul(0xD1B5_4A32_D192_ED03));
}
x ^= x >> 12;
x ^= x << 25;
x ^= x >> 27;
RANDOM_STATE.set(x);
x.wrapping_mul(0x2545_F491_4F6C_DD1D)
}
fn milliseconds_since_epoch() -> i64 {
std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.map_or(0, |elapsed| elapsed.as_millis() as i64)
}
}