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}