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