1use std::collections::HashMap;
5use std::time::Instant;
6
7use wasmtime::{Caller, Engine, Instance, Linker, Memory, Module, Store, TypedFunc, bail, format_err};
8use wasmtime_wasi::WasiCtx;
9use wasmtime_wasi::p1::{self, WasiP1Ctx};
10
11use crate::assets::Assets;
12use crate::audio::AudioSink;
13use crate::gfx::Gfx;
14use crate::net::Net;
15use crate::storage::Storage;
16
17pub const ABI_VERSION: i32 = 0;
18
19#[derive(Clone, Copy)]
21pub struct Fnv(pub u32);
22
23impl Fnv {
24 pub fn new() -> Self {
25 Fnv(0x811c_9dc5)
26 }
27 pub fn update(&mut self, bytes: &[u8]) {
28 let mut h = self.0;
29 for &b in bytes {
30 h ^= b as u32;
31 h = h.wrapping_mul(0x0100_0193);
32 }
33 self.0 = h;
34 }
35}
36
37#[derive(Default)]
40pub struct RawInput {
41 pub keys: Option<[u8; KEY_STATE_BYTES]>,
43 pub key_events: Vec<(u16, bool)>,
45 pub pointer: Option<Pointer>,
46 pub gamepads: Option<[Gamepad; 4]>,
47}
48
49pub const KEY_STATE_BYTES: usize = 32;
50pub const POINTER_BYTES: usize = 48;
51pub const GAMEPAD_BYTES: usize = 204;
52pub const GAMEPAD_BUTTONS: usize = 32;
53pub const GAMEPAD_AXES: usize = 16;
54
55#[derive(Default, Clone, Copy)]
56pub struct Pointer {
57 pub x: f32,
59 pub y: f32,
60 pub dx: f32,
61 pub dy: f32,
62 pub wheel_x: f32,
63 pub wheel_y: f32,
64 pub buttons: u32,
65 pub pressed: u32,
66 pub released: u32,
67 pub flags: u32,
69 pub drawable: (f32, f32),
71}
72
73#[derive(Default, Clone)]
74pub struct Gamepad {
75 pub connected: bool,
76 pub standard: bool,
77 pub buttons: Vec<f32>,
78 pub axes: Vec<f32>,
79 pub name: String,
80}
81
82pub fn frame_position(x: f32, y: f32, drawable: (f32, f32), frame: (usize, usize)) -> (f32, f32) {
85 if frame.0 == 0 || frame.1 == 0 || drawable.0 <= 0.0 || drawable.1 <= 0.0 {
86 return (x, y);
87 }
88 let (dw, dh, fw, fh) = (drawable.0 as f64, drawable.1 as f64, frame.0 as f64, frame.1 as f64);
89 let scale = (dw / fw).min(dh / fh);
90 let (ox, oy) = ((dw - fw * scale) / 2.0, (dh - fh * scale) / 2.0);
91 (((x as f64 - ox) / scale) as f32, ((y as f64 - oy) / scale) as f32)
92}
93
94pub struct Host {
95 wasi: WasiP1Ctx,
96 memory: Option<Memory>,
97 start: Instant,
98 pub virtual_time_ms: Option<f64>,
100 pub frame_rate: f64,
101 pub pads: [u32; 4],
102 pub text: Option<String>,
105 pub input: RawInput,
106 pub input_mode: u32,
108 pub assets: Assets,
109 asset_names: Option<Vec<String>>,
111 pub params: HashMap<String, String>,
112 pub audio: Option<AudioSink>,
113 pub gfx: Gfx,
114 pub net: Net,
115 pub storage: Storage,
116 pub show_frame: bool,
118 audio_rate: u32,
119 audio_channels: u32,
120 pub rgba: Vec<u8>,
122 pub width: usize,
123 pub height: usize,
124 pub frames_presented: u64,
125 pub hashing: bool,
126 pub video_hash: Fnv,
127 pub audio_hash: Fnv,
128 pub audio_frames: u64,
129}
130
131impl Host {
132 pub fn new(
133 assets: impl Into<Assets>,
134 params: HashMap<String, String>,
135 audio: Option<AudioSink>,
136 gfx: Gfx,
137 net: Net,
138 storage: Storage,
139 ) -> Self {
140 let wasi = WasiCtx::builder().inherit_stdout().inherit_stderr().build_p1();
141 Host {
142 wasi,
143 memory: None,
144 start: Instant::now(),
145 virtual_time_ms: None,
146 frame_rate: 60.0,
147 pads: [0; 4],
148 text: None,
149 input: RawInput::default(),
150 input_mode: 0,
151 assets: assets.into(),
152 asset_names: None,
153 params,
154 audio,
155 gfx,
156 net,
157 storage,
158 show_frame: true,
159 audio_rate: 44100,
160 audio_channels: 2,
161 rgba: Vec::new(),
162 width: 0,
163 height: 0,
164 frames_presented: 0,
165 hashing: true,
166 video_hash: Fnv::new(),
167 audio_hash: Fnv::new(),
168 audio_frames: 0,
169 }
170 }
171}
172
173fn guest_slice<'a>(mem: &'a [u8], ptr: u32, len: u64) -> wasmtime::Result<&'a [u8]> {
175 let start = ptr as u64;
176 let end = start.checked_add(len).ok_or_else(|| format_err!("guest pointer overflow"))?;
177 if end > mem.len() as u64 {
178 bail!("guest access out of bounds: {start:#x}..{end:#x} (memory is {:#x})", mem.len());
179 }
180 Ok(&mem[start as usize..end as usize])
181}
182
183fn memory(caller: &Caller<'_, Host>) -> wasmtime::Result<Memory> {
184 caller.data().memory.ok_or_else(|| format_err!("guest memory not yet available"))
185}
186
187fn guest_str(caller: &Caller<'_, Host>, ptr: u32, len: u32) -> wasmtime::Result<String> {
188 let mem = memory(caller)?;
189 let bytes = guest_slice(mem.data(caller), ptr, len as u64)?;
190 Ok(String::from_utf8_lossy(bytes).into_owned())
191}
192
193fn add_gasm_imports(linker: &mut Linker<Host>) -> wasmtime::Result<()> {
194 linker.func_wrap("gasm", "log", |caller: Caller<'_, Host>, ptr: u32, len: u32| {
195 let msg = guest_str(&caller, ptr, len)?;
196 eprintln!("[guest] {msg}");
197 Ok(())
198 })?;
199
200 linker.func_wrap("gasm", "time_ms", |caller: Caller<'_, Host>| -> f64 {
201 let h = caller.data();
202 h.virtual_time_ms.unwrap_or_else(|| h.start.elapsed().as_secs_f64() * 1000.0)
203 })?;
204
205 linker.func_wrap("gasm", "set_frame_rate", |mut caller: Caller<'_, Host>, hz: f64| {
206 if hz.is_finite() && (1.0..=1000.0).contains(&hz) {
207 caller.data_mut().frame_rate = hz;
208 }
209 })?;
210
211 linker.func_wrap(
212 "gasm",
213 "video_present",
214 |mut caller: Caller<'_, Host>, ptr: u32, w: u32, h: u32, stride: u32| -> wasmtime::Result<()> {
215 let (w, h, stride) = (w as usize, h as usize, stride as usize);
216 if w == 0 || h == 0 || w > 4096 || h > 4096 || stride < w * 4 {
217 bail!("video_present: bad geometry {w}x{h} stride {stride}");
218 }
219 let mem = memory(&caller)?;
220 let (data, host) = mem.data_and_store_mut(&mut caller);
221 let src = guest_slice(data, ptr, (stride * (h - 1) + w * 4) as u64)?;
222 host.rgba.resize(w * h * 4, 0);
223 for y in 0..h {
224 let row = &src[y * stride..y * stride + w * 4];
225 host.rgba[y * w * 4..(y + 1) * w * 4].copy_from_slice(row);
226 }
227 if host.hashing {
228 host.video_hash.update(&host.rgba);
229 }
230 host.width = w;
231 host.height = h;
232 host.frames_presented += 1;
233 Ok(())
234 },
235 )?;
236
237 linker.func_wrap("gasm", "audio_config", |mut caller: Caller<'_, Host>, rate: u32, channels: u32| {
238 let h = caller.data_mut();
239 if (8000..=192_000).contains(&rate) && (channels == 1 || channels == 2) {
240 h.audio_rate = rate;
241 h.audio_channels = channels;
242 if let Some(a) = &mut h.audio {
243 a.configure(rate, channels);
244 }
245 }
246 })?;
247
248 linker.func_wrap(
249 "gasm",
250 "audio_push",
251 |mut caller: Caller<'_, Host>, ptr: u32, frames: u32| -> wasmtime::Result<()> {
252 let mem = memory(&caller)?;
253 let (data, host) = mem.data_and_store_mut(&mut caller);
254 let len = frames as u64 * host.audio_channels as u64 * 4;
255 let bytes = guest_slice(data, ptr, len)?;
256 if host.hashing {
257 host.audio_hash.update(bytes);
258 }
259 host.audio_frames += frames as u64;
260 if let Some(a) = &mut host.audio {
261 a.push_le_f32(bytes);
262 }
263 Ok(())
264 },
265 )?;
266
267 linker.func_wrap("gasm", "input_pad", |caller: Caller<'_, Host>, player: u32| -> u32 {
268 caller.data().pads.get(player as usize).copied().unwrap_or(0)
269 })?;
270
271 linker.func_wrap(
272 "gasm",
273 "text_input",
274 |mut caller: Caller<'_, Host>, dst: u32, cap: u32| -> wasmtime::Result<i32> {
275 let mem = memory(&caller)?;
276 let (data, host) = mem.data_and_store_mut(&mut caller);
277 let Some(t) = &host.text else { return Ok(-1) };
278 let t = t.as_bytes();
279 if t.len() <= cap as usize {
280 guest_slice(data, dst, t.len() as u64)?;
281 data[dst as usize..dst as usize + t.len()].copy_from_slice(t);
282 }
283 Ok(t.len() as i32)
284 },
285 )?;
286
287 linker.func_wrap("gasm", "input_mode", |mut caller: Caller<'_, Host>, flags: u32| {
288 caller.data_mut().input_mode = flags & 7;
289 })?;
290
291 linker.func_wrap(
292 "gasm",
293 "key_state",
294 |mut caller: Caller<'_, Host>, dst: u32, len: u32| -> wasmtime::Result<i32> {
295 let mem = memory(&caller)?;
296 let (data, host) = mem.data_and_store_mut(&mut caller);
297 let Some(keys) = host.input.keys else { return Ok(-1) };
298 let n = (len as usize).min(KEY_STATE_BYTES);
299 guest_slice(data, dst, n as u64)?;
300 data[dst as usize..dst as usize + n].copy_from_slice(&keys[..n]);
301 Ok(KEY_STATE_BYTES as i32)
302 },
303 )?;
304
305 linker.func_wrap(
306 "gasm",
307 "key_events",
308 |mut caller: Caller<'_, Host>, dst: u32, cap: u32| -> wasmtime::Result<i32> {
309 let mem = memory(&caller)?;
310 let (data, host) = mem.data_and_store_mut(&mut caller);
311 if host.input.keys.is_none() {
312 return Ok(-1);
313 }
314 let bytes: Vec<u8> = host.input.key_events.iter().flat_map(|&(c, d)| { let c = c.to_le_bytes(); [c[0], c[1], d as u8, 0] }).collect();
315 if bytes.len() <= cap as usize {
316 guest_slice(data, dst, bytes.len() as u64)?;
317 data[dst as usize..dst as usize + bytes.len()].copy_from_slice(&bytes);
318 }
319 Ok(bytes.len() as i32)
320 },
321 )?;
322
323 linker.func_wrap(
324 "gasm",
325 "pointer",
326 |mut caller: Caller<'_, Host>, dst: u32, cap: u32| -> wasmtime::Result<i32> {
327 let mem = memory(&caller)?;
328 let (data, host) = mem.data_and_store_mut(&mut caller);
329 let Some(p) = host.input.pointer else { return Ok(-1) };
330 if cap as usize >= POINTER_BYTES {
331 let (fx, fy) = frame_position(p.x, p.y, p.drawable, (host.width, host.height));
332 let mut b = Vec::with_capacity(POINTER_BYTES);
333 for v in [p.x, p.y, fx, fy, p.dx, p.dy, p.wheel_x, p.wheel_y] {
334 b.extend_from_slice(&v.to_le_bytes());
335 }
336 for v in [p.buttons, p.pressed, p.released, p.flags] {
337 b.extend_from_slice(&v.to_le_bytes());
338 }
339 guest_slice(data, dst, POINTER_BYTES as u64)?;
340 data[dst as usize..dst as usize + POINTER_BYTES].copy_from_slice(&b);
341 }
342 Ok(POINTER_BYTES as i32)
343 },
344 )?;
345
346 linker.func_wrap(
347 "gasm",
348 "gamepad",
349 |mut caller: Caller<'_, Host>, slot: u32, dst: u32, cap: u32| -> wasmtime::Result<i32> {
350 let mem = memory(&caller)?;
351 let (data, host) = mem.data_and_store_mut(&mut caller);
352 let (Some(pads), true) = (&host.input.gamepads, slot < 4) else { return Ok(-1) };
353 if cap as usize >= GAMEPAD_BYTES {
354 let g = &pads[slot as usize];
355 let nb = g.buttons.len().min(GAMEPAD_BUTTONS);
356 let na = g.axes.len().min(GAMEPAD_AXES);
357 let flags = if g.connected { 1 | if g.standard { 2 } else { 0 } } else { 0u32 };
358 let mut b = Vec::with_capacity(GAMEPAD_BYTES);
359 for v in [flags, nb as u32, na as u32] {
360 b.extend_from_slice(&v.to_le_bytes());
361 }
362 for i in 0..GAMEPAD_BUTTONS {
363 b.extend_from_slice(&g.buttons.get(i).copied().unwrap_or(0.0).to_le_bytes());
364 }
365 for i in 0..GAMEPAD_AXES {
366 b.extend_from_slice(&g.axes.get(i).copied().unwrap_or(0.0).to_le_bytes());
367 }
368 guest_slice(data, dst, GAMEPAD_BYTES as u64)?;
369 data[dst as usize..dst as usize + GAMEPAD_BYTES].copy_from_slice(&b);
370 }
371 Ok(GAMEPAD_BYTES as i32)
372 },
373 )?;
374
375 linker.func_wrap(
376 "gasm",
377 "gamepad_name",
378 |mut caller: Caller<'_, Host>, slot: u32, dst: u32, cap: u32| -> wasmtime::Result<i32> {
379 let mem = memory(&caller)?;
380 let (data, host) = mem.data_and_store_mut(&mut caller);
381 let Some(g) = host.input.gamepads.as_ref().and_then(|p| p.get(slot as usize)).filter(|g| g.connected) else { return Ok(-1) };
382 let n = g.name.as_bytes();
383 if n.len() <= cap as usize {
384 guest_slice(data, dst, n.len() as u64)?;
385 data[dst as usize..dst as usize + n.len()].copy_from_slice(n);
386 }
387 Ok(n.len() as i32)
388 },
389 )?;
390
391 linker.func_wrap(
392 "gasm",
393 "param",
394 |mut caller: Caller<'_, Host>, ptr: u32, len: u32, dst: u32, cap: u32| -> wasmtime::Result<i32> {
395 let name = guest_str(&caller, ptr, len)?;
396 let mem = memory(&caller)?;
397 let (data, host) = mem.data_and_store_mut(&mut caller);
398 let Some(v) = host.params.get(&name) else { return Ok(-1) };
399 let v = v.as_bytes();
400 if v.len() <= cap as usize {
401 guest_slice(data, dst, v.len() as u64)?;
402 data[dst as usize..dst as usize + v.len()].copy_from_slice(v);
403 }
404 Ok(v.len() as i32)
405 },
406 )?;
407
408 linker.func_wrap(
409 "gasm",
410 "asset_read_at",
411 |mut caller: Caller<'_, Host>, ptr: u32, len: u32, offset: u32, dst: u32, cap: u32| -> wasmtime::Result<i32> {
412 let name = guest_str(&caller, ptr, len)?;
413 let mem = memory(&caller)?;
414 let (data, host) = mem.data_and_store_mut(&mut caller);
415 let Some(size) = host.assets.size(&name) else { return Ok(-1) };
416 let n = size.saturating_sub(offset as u64).min(cap as u64);
417 guest_slice(data, dst, n)?; let dst = &mut data[dst as usize..dst as usize + n as usize];
419 Ok(host.assets.read_at(&name, offset as u64, dst).map_or(-1, |k| k as i32))
420 },
421 )?;
422
423 linker.func_wrap("gasm", "asset_count", |mut caller: Caller<'_, Host>| -> u32 {
424 let h = caller.data_mut();
425 h.asset_names.get_or_insert_with(|| h.assets.names()).len() as u32
426 })?;
427
428 linker.func_wrap(
429 "gasm",
430 "asset_name",
431 |mut caller: Caller<'_, Host>, index: u32, dst: u32, cap: u32| -> wasmtime::Result<i32> {
432 let mem = memory(&caller)?;
433 let (data, host) = mem.data_and_store_mut(&mut caller);
434 let names = host.asset_names.get_or_insert_with(|| host.assets.names());
435 let Some(n) = names.get(index as usize) else { return Ok(-1) };
436 let n = n.as_bytes();
437 if n.len() <= cap as usize {
438 guest_slice(data, dst, n.len() as u64)?;
439 data[dst as usize..dst as usize + n.len()].copy_from_slice(n);
440 }
441 Ok(n.len() as i32)
442 },
443 )?;
444
445 linker.func_wrap(
446 "gasm",
447 "asset_size",
448 |caller: Caller<'_, Host>, ptr: u32, len: u32| -> wasmtime::Result<i32> {
449 let name = guest_str(&caller, ptr, len)?;
450 Ok(caller.data().assets.size(&name).map_or(-1, |n| n as i32))
451 },
452 )?;
453
454 linker.func_wrap(
455 "gasm",
456 "asset_read",
457 |mut caller: Caller<'_, Host>, ptr: u32, len: u32, dst: u32, cap: u32| -> wasmtime::Result<i32> {
458 let name = guest_str(&caller, ptr, len)?;
459 let mem = memory(&caller)?;
460 let (data, host) = mem.data_and_store_mut(&mut caller);
461 let Some(size) = host.assets.size(&name) else { return Ok(-1) };
462 let n = size.min(cap as u64);
463 guest_slice(data, dst, n)?;
464 let dst = &mut data[dst as usize..dst as usize + n as usize];
465 Ok(host.assets.read_at(&name, 0, dst).map_or(-1, |k| k as i32))
466 },
467 )?;
468 Ok(())
469}
470
471pub fn precompile(wasm: &[u8]) -> wasmtime::Result<Vec<u8>> {
473 Engine::default().precompile_module(wasm)
474}
475
476fn trap(e: String) -> wasmtime::Error {
477 format_err!("{e}")
478}
479
480fn add_gfx_imports(linker: &mut Linker<Host>) -> wasmtime::Result<()> {
481 const M: &str = "gasm:gfx";
482 linker.func_wrap(M, "width", |c: Caller<'_, Host>| c.data().gfx.size().0)?;
483 linker.func_wrap(M, "height", |c: Caller<'_, Host>| c.data().gfx.size().1)?;
484 linker.func_wrap(M, "create_shader", |mut c: Caller<'_, Host>, ptr: u32, len: u32| -> wasmtime::Result<u32> {
485 let src = guest_str(&c, ptr, len)?;
486 c.data_mut().gfx.create_shader(&src).map_err(trap)
487 })?;
488 linker.func_wrap(M, "create_buffer", |mut c: Caller<'_, Host>, size: u32, usage: u32| -> wasmtime::Result<u32> {
489 c.data_mut().gfx.create_buffer(size, usage).map_err(trap)
490 })?;
491 linker.func_wrap(M, "create_pipeline", |mut c: Caller<'_, Host>, ptr: u32, len: u32| -> wasmtime::Result<u32> {
492 let json = guest_str(&c, ptr, len)?;
493 c.data_mut().gfx.create_pipeline(&json).map_err(trap)
494 })?;
495 linker.func_wrap(M, "create_bind_group", |mut c: Caller<'_, Host>, ptr: u32, len: u32| -> wasmtime::Result<u32> {
496 let json = guest_str(&c, ptr, len)?;
497 c.data_mut().gfx.create_bind_group(&json).map_err(trap)
498 })?;
499 linker.func_wrap(
500 M,
501 "write_buffer",
502 |mut c: Caller<'_, Host>, buf: u32, offset: u32, ptr: u32, len: u32| -> wasmtime::Result<()> {
503 let mem = memory(&c)?;
504 let (data, host) = mem.data_and_store_mut(&mut c);
505 let bytes = guest_slice(data, ptr, len as u64)?;
506 if host.hashing {
507 host.video_hash.update(bytes);
508 }
509 host.gfx.write_buffer(buf, offset, bytes).map_err(trap)
510 },
511 )?;
512 linker.func_wrap(M, "create_bind_group_layout", |mut c: Caller<'_, Host>, ptr: u32, len: u32| -> wasmtime::Result<u32> {
513 let json = guest_str(&c, ptr, len)?;
514 c.data_mut().gfx.create_bind_group_layout(&json).map_err(trap)
515 })?;
516 linker.func_wrap(M, "create_texture", |mut c: Caller<'_, Host>, ptr: u32, len: u32| -> wasmtime::Result<u32> {
517 let json = guest_str(&c, ptr, len)?;
518 c.data_mut().gfx.create_texture(&json).map_err(trap)
519 })?;
520 linker.func_wrap(M, "create_sampler", |mut c: Caller<'_, Host>, ptr: u32, len: u32| -> wasmtime::Result<u32> {
521 let json = guest_str(&c, ptr, len)?;
522 c.data_mut().gfx.create_sampler(&json).map_err(trap)
523 })?;
524 linker.func_wrap(
525 M,
526 "write_texture",
527 |mut c: Caller<'_, Host>, tex: u32, mip: u32, x: u32, y: u32, w: u32, h: u32, ptr: u32, len: u32| -> wasmtime::Result<()> {
528 let mem = memory(&c)?;
529 let (data, host) = mem.data_and_store_mut(&mut c);
530 let bytes = guest_slice(data, ptr, len as u64)?;
531 host.gfx.write_texture(tex, mip, x, y, w, h, bytes).map_err(trap)?;
532 if host.hashing {
533 for v in [tex, mip, x, y, w, h] {
535 host.video_hash.update(&v.to_le_bytes());
536 }
537 host.video_hash.update(bytes);
538 }
539 Ok(())
540 },
541 )?;
542 linker.func_wrap(M, "begin_frame", |mut c: Caller<'_, Host>, r: f32, g: f32, b: f32, a: f32| -> wasmtime::Result<u32> {
543 let h = c.data_mut();
544 let show = h.show_frame;
545 h.gfx.begin_frame([r, g, b, a], show).map(u32::from).map_err(trap)
546 })?;
547 linker.func_wrap(M, "set_pipeline", |mut c: Caller<'_, Host>, p: u32| -> wasmtime::Result<()> {
548 c.data_mut().gfx.set_pipeline(p).map_err(trap)
549 })?;
550 linker.func_wrap(M, "set_bind_group", |mut c: Caller<'_, Host>, i: u32, bg: u32| -> wasmtime::Result<()> {
551 c.data_mut().gfx.set_bind_group(i, bg).map_err(trap)
552 })?;
553 linker.func_wrap(
554 M,
555 "set_bind_group_offsets",
556 |mut c: Caller<'_, Host>, i: u32, bg: u32, ptr: u32, count: u32| -> wasmtime::Result<()> {
557 let mem = memory(&c)?;
558 let (data, host) = mem.data_and_store_mut(&mut c);
559 let bytes = guest_slice(data, ptr, count as u64 * 4)?;
560 let offsets: Vec<u32> = bytes.chunks_exact(4).map(|b| u32::from_le_bytes([b[0], b[1], b[2], b[3]])).collect();
561 host.gfx.set_bind_group_offsets(i, bg, &offsets).map_err(trap)
562 },
563 )?;
564 linker.func_wrap(
565 M,
566 "set_viewport",
567 |mut c: Caller<'_, Host>, x: f32, y: f32, w: f32, h: f32, min: f32, max: f32| -> wasmtime::Result<()> {
568 c.data_mut().gfx.set_viewport(x, y, w, h, min, max).map_err(trap)
569 },
570 )?;
571 linker.func_wrap(M, "set_scissor_rect", |mut c: Caller<'_, Host>, x: u32, y: u32, w: u32, h: u32| -> wasmtime::Result<()> {
572 c.data_mut().gfx.set_scissor_rect(x, y, w, h).map_err(trap)
573 })?;
574 linker.func_wrap(M, "set_vertex_buffer", |mut c: Caller<'_, Host>, slot: u32, b: u32, off: u32| -> wasmtime::Result<()> {
575 c.data_mut().gfx.set_vertex_buffer(slot, b, off).map_err(trap)
576 })?;
577 linker.func_wrap(M, "set_index_buffer", |mut c: Caller<'_, Host>, b: u32, fmt: u32, off: u32| -> wasmtime::Result<()> {
578 c.data_mut().gfx.set_index_buffer(b, fmt, off).map_err(trap)
579 })?;
580 linker.func_wrap(M, "draw", |mut c: Caller<'_, Host>, vc: u32, ic: u32, fv: u32, fi: u32| {
581 c.data_mut().gfx.draw(vc, ic, fv, fi)
582 })?;
583 linker.func_wrap(M, "draw_indexed", |mut c: Caller<'_, Host>, ic: u32, n: u32, first: u32, base: i32, fi: u32| {
584 c.data_mut().gfx.draw_indexed(ic, n, first, base, fi)
585 })?;
586 linker.func_wrap(M, "end_frame", |mut c: Caller<'_, Host>| -> wasmtime::Result<()> {
587 let h = c.data_mut();
588 h.frames_presented += 1;
589 h.gfx.end_frame().map_err(trap)
590 })?;
591 Ok(())
592}
593
594fn add_net_imports(linker: &mut Linker<Host>) -> wasmtime::Result<()> {
595 const M: &str = "gasm:net";
596 linker.func_wrap(M, "open", |mut c: Caller<'_, Host>, ptr: u32, len: u32| -> wasmtime::Result<i32> {
597 let url = guest_str(&c, ptr, len)?;
598 Ok(c.data_mut().net.open(&url))
599 })?;
600 linker.func_wrap(M, "state", |c: Caller<'_, Host>, h: i32| c.data().net.state(h))?;
601 linker.func_wrap(M, "send", |mut c: Caller<'_, Host>, h: i32, ptr: u32, len: u32| -> wasmtime::Result<i32> {
602 if len == 0 {
603 return Ok(-1);
604 }
605 let mem = memory(&c)?;
606 let msg = guest_slice(mem.data(&c), ptr, len as u64)?.to_vec();
607 Ok(c.data_mut().net.send(h, msg))
608 })?;
609 linker.func_wrap(M, "recv", |mut c: Caller<'_, Host>, h: i32, dst: u32, cap: u32| -> wasmtime::Result<i32> {
610 let mem = memory(&c)?;
611 let (data, host) = mem.data_and_store_mut(&mut c);
612 let msg = match host.net.peek(h) {
613 Ok(m) => m,
614 Err(code) => return Ok(code),
615 };
616 let n = msg.len();
617 if n <= cap as usize {
618 guest_slice(data, dst, n as u64)?;
619 data[dst as usize..dst as usize + n].copy_from_slice(msg);
620 host.net.pop(h);
621 }
622 Ok(n as i32)
623 })?;
624 linker.func_wrap(M, "close", |mut c: Caller<'_, Host>, h: i32| c.data_mut().net.close(h))?;
625 Ok(())
626}
627
628fn add_storage_imports(linker: &mut Linker<Host>) -> wasmtime::Result<()> {
629 const M: &str = "gasm:storage";
630 linker.func_wrap(M, "get", |mut c: Caller<'_, Host>, kp: u32, kl: u32, dst: u32, cap: u32| -> wasmtime::Result<i32> {
631 let key = guest_str(&c, kp, kl)?;
632 let mem = memory(&c)?;
633 let (data, host) = mem.data_and_store_mut(&mut c);
634 let Some(v) = host.storage.get(&key) else { return Ok(-1) };
635 if v.len() <= cap as usize {
636 guest_slice(data, dst, v.len() as u64)?;
637 data[dst as usize..dst as usize + v.len()].copy_from_slice(v);
638 }
639 Ok(v.len() as i32)
640 })?;
641 linker.func_wrap(M, "set", |mut c: Caller<'_, Host>, kp: u32, kl: u32, vp: u32, vl: u32| -> wasmtime::Result<i32> {
642 let key = guest_str(&c, kp, kl)?;
643 let mem = memory(&c)?;
644 let (data, host) = mem.data_and_store_mut(&mut c);
645 let value = guest_slice(data, vp, vl as u64)?;
646 Ok(match host.storage.set(&key, value) {
647 Ok(()) => 0,
648 Err(e) => {
649 eprintln!("[gasm] storage: {e}");
650 -1
651 }
652 })
653 })?;
654 linker.func_wrap(M, "count", |c: Caller<'_, Host>| c.data().storage.keys().len() as u32)?;
655 linker.func_wrap(M, "key", |mut c: Caller<'_, Host>, index: u32, dst: u32, cap: u32| -> wasmtime::Result<i32> {
656 let mem = memory(&c)?;
657 let (data, host) = mem.data_and_store_mut(&mut c);
658 let keys = host.storage.keys();
659 let Some(k) = keys.get(index as usize) else { return Ok(-1) };
660 let k = k.as_bytes();
661 if k.len() <= cap as usize {
662 guest_slice(data, dst, k.len() as u64)?;
663 data[dst as usize..dst as usize + k.len()].copy_from_slice(k);
664 }
665 Ok(k.len() as i32)
666 })?;
667 linker.func_wrap(M, "delete", |mut c: Caller<'_, Host>, kp: u32, kl: u32| -> wasmtime::Result<i32> {
668 let key = guest_str(&c, kp, kl)?;
669 Ok(if c.data_mut().storage.delete(&key) { 0 } else { -1 })
670 })?;
671 Ok(())
672}
673
674#[derive(Debug)]
676pub enum Stop {
677 Exit(i32),
679 Trap(String),
680}
681
682impl std::fmt::Display for Stop {
683 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
684 match self {
685 Stop::Exit(code) => write!(f, "guest exited with code {code}"),
686 Stop::Trap(msg) => write!(f, "guest trapped: {msg}"),
687 }
688 }
689}
690
691impl std::error::Error for Stop {}
692
693fn classify(e: wasmtime::Error) -> Stop {
694 match e.downcast_ref::<wasmtime_wasi::I32Exit>() {
695 Some(exit) => Stop::Exit(exit.0),
696 None => Stop::Trap(format!("{e:?}")),
697 }
698}
699
700pub struct Game {
701 pub store: Store<Host>,
702 frame: TypedFunc<(), ()>,
703 exit: Option<TypedFunc<(), ()>>,
704}
705
706impl Game {
707 pub fn load(wasm: &[u8], host: Host) -> Result<Game, Stop> {
708 Self::load_inner(wasm, host).map_err(classify)
709 }
710
711 fn load_inner(wasm: &[u8], host: Host) -> wasmtime::Result<Game> {
712 let engine = Engine::default();
713 let module = if wasm.starts_with(b"\0asm") {
714 Module::new(&engine, wasm)?
715 } else {
716 unsafe { Module::deserialize(&engine, wasm)? }
719 };
720 let mut linker: Linker<Host> = Linker::new(&engine);
721 p1::add_to_linker_sync(&mut linker, |h: &mut Host| &mut h.wasi)?;
722 add_gasm_imports(&mut linker)?;
723 add_gfx_imports(&mut linker)?;
724 add_net_imports(&mut linker)?;
725 add_storage_imports(&mut linker)?;
726 linker.define_unknown_imports_as_traps(&module)?;
729
730 let mut store = Store::new(&engine, host);
731 let instance: Instance = linker.instantiate(&mut store, &module)?;
732 let memory = instance
733 .get_memory(&mut store, "memory")
734 .ok_or_else(|| format_err!("guest does not export `memory`"))?;
735 store.data_mut().memory = Some(memory);
736
737 if let Ok(init) = instance.get_typed_func::<(), ()>(&mut store, "_initialize") {
738 init.call(&mut store, ())?;
739 }
740 let version = instance
741 .get_typed_func::<(), i32>(&mut store, "gasm_abi_version")?
742 .call(&mut store, ())?;
743 if version != ABI_VERSION {
744 bail!("guest targets gasm ABI v{version}, runner implements v{ABI_VERSION}");
745 }
746 let rc = instance.get_typed_func::<(), i32>(&mut store, "gasm_init")?.call(&mut store, ())?;
747 if rc != 0 {
748 bail!("gasm_init failed with code {rc}");
749 }
750 let frame = instance.get_typed_func::<(), ()>(&mut store, "gasm_frame")?;
751 let exit = instance.get_typed_func::<(), ()>(&mut store, "gasm_exit").ok();
752 Ok(Game { store, frame, exit })
753 }
754
755 pub fn frame(&mut self) -> Result<(), Stop> {
756 self.frame.call(&mut self.store, ()).map_err(classify)
757 }
758
759 pub fn exit(&mut self) {
761 if let Some(f) = self.exit.take() {
762 if let Err(e) = f.call(&mut self.store, ()) {
763 if let Stop::Trap(t) = classify(e) {
764 eprintln!("[gasm] gasm_exit trapped: {t}");
765 }
766 }
767 }
768 }
769
770 pub fn host(&self) -> &Host {
771 self.store.data()
772 }
773
774 pub fn host_mut(&mut self) -> &mut Host {
775 self.store.data_mut()
776 }
777}