Skip to main content

warp_runtime/
host_words.rs

1//! The words of the program's environment, imported from the "host" module and called like C functions:
2//! `sleep_ms(ms)` pauses, `random()` is a float in [0, 1), `random_below(n)` an int in 0..n, `clock()` the milliseconds
3//! since the Unix epoch. A program that defines a word of the same name keeps its own. warp adds the words that need
4//! its compiler, the network or threads (host.rs); these need nothing.
5//! `signal_poll()` and `signal_listen(kind, id, argument)` serve the handlers of system signals (system_signals.rs).
6pub const HOST_LIBRARY: &str = "host";
7/// foreign_call(runtime, module, member, call, arguments): a module of another runtime (foreign.rs), Nodes in and out
8pub const FOREIGN_CALL: &str = "foreign_call";
9/// `sleep(seconds)`, the prelude word; it calls `sleep_ms(milliseconds)`, the host word that pauses
10pub const SLEEP: &str = "sleep";
11pub const SLEEP_MS: &str = "sleep_ms";
12pub const RANDOM: &str = "random";
13pub const RANDOM_BELOW: &str = "random_below";
14/// `random_seed(n)`: random and random_below give the same numbers after the same seed (natively and in the browser)
15pub const RANDOM_SEED: &str = "random_seed";
16pub const CLOCK: &str = "clock";
17/// `local_offset(ms)`: seconds east of UTC in the environment's time zone (TZ) at that instant: an instant's wall clock
18pub const LOCAL_OFFSET: &str = "local_offset";
19pub const SIGNAL_POLL: &str = "signal_poll";
20/// `signal_listen(kind, id, argument)`: starts the timer on·every·id, `on every 5 seconds {…}` (LISTEN_EVERY, ms),
21/// `on frame {…}` (LISTEN_FRAME, ms; every frame of the paint window while it shows, without a window once),
22/// `on every monday at 9:00 {…}` and `at 9:00 {…}` (LISTEN_CLOCK, weekdays · CLOCK_WEEKDAYS + minute of day, the
23/// weekdays a mask with bit 0 Sunday … bit 6 Saturday, 0 once at the next such local time)
24pub const SIGNAL_LISTEN: &str = "signal_listen";
25pub const LISTEN_EVERY: i64 = 0;
26pub const LISTEN_FRAME: i64 = 1;
27pub const LISTEN_CLOCK: i64 = 2;
28/// The clock argument's weekdays count in ten-thousands: `(signal_listen 2 0 1271290)` is 21:30 (1290) every day (127)
29pub const CLOCK_WEEKDAYS: i64 = 10_000;
30pub const SIGNAL_WATCH: &str = "signal_watch";
31/// `system_value(name)`: the machine's `battery` percent, `charging`, `online`, `dark mode` (system_values.rs)
32pub const SYSTEM_VALUE: &str = "system_value";
33/// `set_system_value(name, value)`: writes one, `window.width = 800` resizes the paint window (system_values.rs write)
34pub const SET_SYSTEM_VALUE: &str = "set_system_value";
35pub const BATTERY: &str = "battery";
36pub const CHARGING: &str = "charging";
37pub const ONLINE: &str = "online";
38pub const DARK_MODE: &str = "dark mode";
39/// How often the clipboard changed since the machine started (macOS NSPasteboard changeCount): `on clipboard change`
40pub const CLIPBOARD_COUNT: &str = "clipboard count";
41/// The pointer over the page's canvas, in canvas pixels, and whether a button is down (card drawing-frames)
42pub const MOUSE_X: &str = "mouse_x";
43pub const MOUSE_Y: &str = "mouse_y";
44pub const MOUSE_DOWN: &str = "mouse_down";
45/// The wheel turned over the paint window or the page's canvas since the start, in steps (notches): up (away from you)
46/// adds, down takes away; a trackpad's scroll counts PIXELS_PER_WHEEL_STEP pixels a step (card shader-wheel)
47pub const MOUSE_WHEEL: &str = "mouse_wheel";
48pub const PIXELS_PER_WHEEL_STEP: f64 = 100.0;
49/// Whether a key is held now, over the paint window or the page (card keyboard-state): `on key` hears each press
50pub const KEY_DOWN: &str = "key_down";
51/// The name of the key held now ("a", "ArrowUp", "" for none), as `on key` names presses (card held-key): the word
52/// a program reads, made from KEY_CODE_HELD, the held key's code (0: none), which it does not name
53pub const HELD_KEY: &str = "held_key";
54pub const KEY_CODE_HELD: &str = "key code held";
55/// `key_held(code)`: whether that key is held now, among others (card allow-diagonals: `held_key == LEFT` while UP is
56/// held too); code 0 whether none is
57pub const KEY_HELD: &str = "key_held";
58/// Whether a window shows the pictures: natively the paint window until it closes (no before the first paint and
59/// headless), on a page its canvas, always; `while window_open { … }` is a drawing loop that ends with its window
60pub const WINDOW_OPEN: &str = "window_open";
61/// The local time of day in milliseconds since midnight, what the word `time` is compared with a duration (src/numbers/units.rs)
62pub const TIME_OF_DAY: &str = "time of day";
63/// the size a picture gets: the playground's output pane, natively the paint window's first size (system_values.rs)
64pub const VIEW_WIDTH: &str = "view_width";
65pub const VIEW_HEIGHT: &str = "view_height";
66/// Whether `use draw` draws on the GPU (src/graphics/gpu_canvas.rs): natively in a window, or as WARP_DRAW says; never on a page
67pub const GPU_DRAWING: &str = "gpu_drawing";
68/// window.width/height (card make-width): the paint window's inner size in points (the playground: the paintings' frame
69/// in CSS pixels), writable; display.width/height the screen's, read only (lowering/words/module_properties.rs)
70pub const WINDOW_WIDTH: &str = "window_width";
71pub const WINDOW_HEIGHT: &str = "window_height";
72pub const DISPLAY_WIDTH: &str = "display_width";
73pub const DISPLAY_HEIGHT: &str = "display_height";
74/// display.dpi: the screen's device pixels per inch, which turn `px` into lengths (decision dimensions-units)
75pub const DISPLAY_DPI: &str = "display_dpi";
76/// The next key pressed over the paint window, its code (0: none left), each press read once: natively `on key {…}`
77/// (lowering/signals/system_signals.rs); not a value a program names
78pub const KEY_PRESSED: &str = "key_pressed";
79/// Whether a shader can paint here (1) or not (0): `paint(f, w, h)` of a warp function reads it before painting f's
80/// fragment shader, else paints f on the CPU (card paint-function, src/lowering/foreign/gpu_maps/kernels.rs); not a value
81/// a program names
82pub const GPU_PAINTS: &str = "gpu paints";
83/// Read once as a program naming the Escape key starts (`key == ESC`): Escape over the paint window is then its key,
84/// else it ends the run (card escape-window, src/graphics/paint_window.rs); not a value a program names
85pub const ESCAPE_KEPT: &str = "escape kept";
86/// Whether a click over the paint window is left to read (1, then it is the click read) or none (0), each click read
87/// once, and that click's place: natively `on click {…}` (lowering/signals/system_signals.rs); not values a program names
88pub const CLICK_PRESSED: &str = "click_pressed";
89pub const CLICK_X: &str = "click_x";
90pub const CLICK_Y: &str = "click_y";
91/// The system values, and whether each is a yes/no value
92pub const SYSTEM_VALUES: [(&str, bool); 19] = [
93	(BATTERY, false),
94	(CHARGING, true),
95	(ONLINE, true),
96	(DARK_MODE, true),
97	(CLIPBOARD_COUNT, false),
98	(MOUSE_X, false),
99	(MOUSE_Y, false),
100	(MOUSE_DOWN, true),
101	(MOUSE_WHEEL, false),
102	(KEY_DOWN, true),
103	(WINDOW_OPEN, true),
104	(VIEW_WIDTH, false),
105	(VIEW_HEIGHT, false),
106	(GPU_DRAWING, true),
107	(WINDOW_WIDTH, false),
108	(WINDOW_HEIGHT, false),
109	(DISPLAY_WIDTH, false),
110	(DISPLAY_HEIGHT, false),
111	(DISPLAY_DPI, false),
112];
113/// `clipboard`: the clipboard's text, read only when the program reads it (host.rs clipboard_text)
114pub const CLIPBOARD: &str = "clipboard";
115pub const CLIPBOARD_TEXT: &str = "clipboard_text";
116/// The path of the page shown ("/users/7"), which picks its route (lowering/web/routes.rs); "/" natively
117pub const PAGE_PATH: &str = "page_path";
118/// `notify "text"`: a desktop notification (system_values.rs notify; the playground: the browser's Notification)
119pub const NOTIFY: &str = "notify";
120/// `gpu_compute(shader, numbers, workgroups)`: a WGSL compute shader run over the numbers on the GPU (WebGPU in the
121/// browser, web/playground/host-gpu.js), the numbers it left (card web-apis, notes/web_framework.md "web-apis: WebGPU")
122pub const GPU_COMPUTE: &str = "gpu_compute";
123/// `gpu_render(shader, width, height)`: a WGSL fragment shader `main` run over every pixel, the pixels paint shows
124/// (0xFFRRGGBB row by row; card g_YqWY, notes/web_framework.md "web-apis: WebGPU")
125pub const GPU_RENDER: &str = "gpu_render";
126/// `text_coverage(words, size)`: the words set in a sans-serif font `size` pixels high (card paint-text, lib/draw.warp
127/// label): [width, height, then how much each pixel is covered, 0 to 255, row by row]; the browser's canvas 2D fillText
128/// in the playground, a system font through ab_glyph natively (src/graphics/text_raster.rs)
129pub const TEXT_COVERAGE: &str = "text_coverage";
130/// `gpu_compute(shader, xs, workgroups)` of a `linear xs = float[n]`: the shader runs over the cells of the block in linear
131/// memory, read and written in place without building a list (card gpu-vectors, notes/gpu.md); gives the block back
132pub const GPU_COMPUTE_LINEAR: &str = "gpu_compute_linear";
133/// `ys = xs.map(x => …) @gpu` of a linear float array (src/lowering/foreign/gpu_maps.rs): the WGSL kernel run over xs's cells,
134/// written into ys's; gives 1, or 0 without an adapter (said once), when the CPU maps them instead
135pub const GPU_MAP_LINEAR: &str = "gpu_map_linear";
136/// `s = sum(xs.map(x => …) @gpu)`, min, max (src/lowering/foreign/gpu_maps.rs): the kernel leaves one partial result per
137/// workgroup, written into a block the program combines; 1, or 0 without an adapter, as gpu_map_linear
138pub const GPU_REDUCE_LINEAR: &str = "gpu_reduce_linear";
139/// What every exported handler name starts with
140pub const HANDLER_PREFIX: &str = "on·";
141/// `on·file·0`: the handler of the first `on file "x" change {…}`
142pub const FILE_HANDLER_PREFIX: &str = "on·file·";
143/// `exit(code)` ends the run, not the process (P121, system_signals.rs ExitRequest)
144pub const EXIT: &str = "exit";
145/// The exported handler of `on interrupt {…}` (src/lowering/signals/event_signals.rs)
146pub const INTERRUPT_HANDLER: &str = "on·interrupt";
147/// The exported handler of `on exit {…}`, run once as the run ends (system_signals.rs run_exit_handler)
148pub const EXIT_HANDLER: &str = "on·exit";
149/// `on·every·0`: the handler of the first `on every … {…}`
150pub const TIMER_HANDLER_PREFIX: &str = "on·every·";
151/// `on·fetch·0`: the handler run when the reply of the first `users := fetch url` arrived (src/web/fetches.rs)
152pub const FETCH_HANDLER_PREFIX: &str = "on·fetch·";
153/// The checks of the listeners on shared values (P106), polled at every check point (lowering/signals/signal_values.rs)
154pub const SHARED_HANDLER: &str = "on·shared";
155/// The words link_host_words provides
156pub const BASIC_HOST_WORDS: [&str; 13] = [
157	SLEEP_MS,
158	RANDOM,
159	RANDOM_BELOW,
160	RANDOM_SEED,
161	CLOCK,
162	LOCAL_OFFSET,
163	SIGNAL_POLL,
164	SIGNAL_LISTEN,
165	SIGNAL_WATCH,
166	SYSTEM_VALUE,
167	SET_SYSTEM_VALUE,
168	KEY_HELD,
169	EXIT,
170];
171
172#[cfg(feature = "engine")]
173pub use linking::*;
174
175#[cfg(feature = "engine")]
176mod linking {
177	use super::*;
178	use crate::system_signals;
179	use std::time::Duration;
180	use wasmtime::{Caller, Func, Linker, Result};
181
182	/// Link sleep, random, random_below and clock, for a store of any state
183	pub fn link_host_words<T: 'static>(linker: &mut Linker<T>) -> Result<()> {
184		system_signals::forget_timers(); // each run links anew, on its own thread
185		RANDOM_STATE.set(0); // and starts unseeded
186		linker.func_wrap(HOST_LIBRARY, SLEEP_MS, |mut caller: Caller<'_, T>, milliseconds: i64| {
187			system_signals::end_at_first_frame()?;
188			system_signals::sleep_with_handlers(
189				&mut caller,
190				Duration::from_millis(milliseconds.max(0) as u64),
191				exported,
192			)
193		})?;
194		// the top 53 bits make a uniform f64 in [0, 1)
195		linker.func_wrap(HOST_LIBRARY, RANDOM, || (next_random() >> 11) as f64 / (1u64 << 53) as f64)?;
196		linker.func_wrap(HOST_LIBRARY, RANDOM_BELOW, |bound: i64| {
197			if bound <= 0 {
198				0
199			} else {
200				(next_random() % bound as u64) as i64
201			}
202		})?;
203		linker.func_wrap(HOST_LIBRARY, RANDOM_SEED, |seed: i64| RANDOM_STATE.set(seeded_state(seed as u64)))?;
204		linker.func_wrap(HOST_LIBRARY, CLOCK, milliseconds_since_epoch)?;
205		linker.func_wrap(HOST_LIBRARY, LOCAL_OFFSET, system_signals::local_offset_at)?;
206		linker.func_wrap(HOST_LIBRARY, SIGNAL_POLL, |mut caller: Caller<'_, T>| {
207			system_signals::run_due_handlers(&mut caller, exported)
208		})?;
209		linker.func_wrap(HOST_LIBRARY, SIGNAL_LISTEN, system_signals::listen)?;
210		linker.func_wrap(
211			HOST_LIBRARY,
212			SIGNAL_WATCH,
213			|mut caller: Caller<'_, T>, id: i64, path: i32| -> Result<()> {
214				let path = c_string(&mut caller, path)?;
215				system_signals::watch_file(id, path);
216				Ok(())
217			},
218		)?;
219		linker.func_wrap(HOST_LIBRARY, SYSTEM_VALUE, |mut caller: Caller<'_, T>, name: i32| -> Result<i64> {
220			let name = c_string(&mut caller, name)?;
221			crate::system_values::read(&name).map_err(wasmtime::Error::msg)
222		})?;
223		linker.func_wrap(
224			HOST_LIBRARY,
225			SET_SYSTEM_VALUE,
226			|mut caller: Caller<'_, T>, name: i32, value: i64| -> Result<()> {
227				let name = c_string(&mut caller, name)?;
228				crate::system_values::write(&name, value).map_err(wasmtime::Error::msg)
229			},
230		)?;
231		linker.func_wrap(HOST_LIBRARY, KEY_HELD, |code: i64| crate::system_values::key_held(code) as i64)?;
232		linker.func_wrap(HOST_LIBRARY, EXIT, |code: i64| -> Result<()> {
233			Err(wasmtime::Error::new(system_signals::ExitRequest(code as i32)))
234		})?;
235		Ok(())
236	}
237
238	/// The NUL-terminated text a text literal passes as an i32 (the emitter's C strings)
239	fn c_string<T>(caller: &mut Caller<'_, T>, pointer: i32) -> Result<String> {
240		let memory = caller
241			.get_export("memory")
242			.and_then(|export| export.into_memory())
243			.ok_or_else(|| wasmtime::Error::msg("no memory export"))?;
244		let bytes = memory.data(&*caller);
245		let start = (pointer as usize).min(bytes.len());
246		let end = bytes[start..].iter().position(|byte| *byte == 0).map_or(bytes.len(), |length| start + length);
247		Ok(String::from_utf8_lossy(&bytes[start..end]).into_owned())
248	}
249
250	fn exported<T>(caller: &mut Caller<'_, T>, name: &str) -> Option<Func> {
251		caller.get_export(name).and_then(|export| export.into_func())
252	}
253
254	thread_local! {
255		/// per run: a seeded run is not disturbed by the runs of other threads
256		static RANDOM_STATE: std::cell::Cell<u64> = const { std::cell::Cell::new(0) };
257	}
258
259	/// Runs that started unseeded, so two in the same millisecond differ
260	static UNSEEDED_RUNS: std::sync::atomic::AtomicU64 = std::sync::atomic::AtomicU64::new(0);
261
262	/// The generator's state after `random_seed(seed)`, never 0 (web/playground/host.js seedRandom does the same)
263	fn seeded_state(seed: u64) -> u64 {
264		seed.wrapping_mul(0x9E37_79B9_7F4A_7C15) | 1
265	}
266
267	/// xorshift64*, seeded from the clock once per run unless random_seed set it: random enough for games and
268	/// samples, not for secrets
269	fn next_random() -> u64 {
270		let mut x = RANDOM_STATE.get();
271		if x == 0 {
272			let run = UNSEEDED_RUNS.fetch_add(1, std::sync::atomic::Ordering::Relaxed);
273			x = seeded_state(milliseconds_since_epoch() as u64 ^ run.wrapping_mul(0xD1B5_4A32_D192_ED03));
274		}
275		x ^= x >> 12;
276		x ^= x << 25;
277		x ^= x >> 27;
278		RANDOM_STATE.set(x);
279		x.wrapping_mul(0x2545_F491_4F6C_DD1D)
280	}
281
282	fn milliseconds_since_epoch() -> i64 {
283		std::time::SystemTime::now()
284			.duration_since(std::time::UNIX_EPOCH)
285			.map_or(0, |elapsed| elapsed.as_millis() as i64)
286	}
287}