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}