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
37pub struct Host {
38 wasi: WasiP1Ctx,
39 memory: Option<Memory>,
40 start: Instant,
41 pub virtual_time_ms: Option<f64>,
43 pub frame_rate: f64,
44 pub pads: [u32; 4],
45 pub text: Option<String>,
48 pub assets: Assets,
49 asset_names: Option<Vec<String>>,
51 pub params: HashMap<String, String>,
52 pub audio: Option<AudioSink>,
53 pub gfx: Gfx,
54 pub net: Net,
55 pub storage: Storage,
56 pub show_frame: bool,
58 audio_rate: u32,
59 audio_channels: u32,
60 pub rgba: Vec<u8>,
62 pub width: usize,
63 pub height: usize,
64 pub frames_presented: u64,
65 pub hashing: bool,
66 pub video_hash: Fnv,
67 pub audio_hash: Fnv,
68 pub audio_frames: u64,
69}
70
71impl Host {
72 pub fn new(
73 assets: impl Into<Assets>,
74 params: HashMap<String, String>,
75 audio: Option<AudioSink>,
76 gfx: Gfx,
77 net: Net,
78 storage: Storage,
79 ) -> Self {
80 let wasi = WasiCtx::builder().inherit_stdout().inherit_stderr().build_p1();
81 Host {
82 wasi,
83 memory: None,
84 start: Instant::now(),
85 virtual_time_ms: None,
86 frame_rate: 60.0,
87 pads: [0; 4],
88 text: None,
89 assets: assets.into(),
90 asset_names: None,
91 params,
92 audio,
93 gfx,
94 net,
95 storage,
96 show_frame: true,
97 audio_rate: 44100,
98 audio_channels: 2,
99 rgba: Vec::new(),
100 width: 0,
101 height: 0,
102 frames_presented: 0,
103 hashing: true,
104 video_hash: Fnv::new(),
105 audio_hash: Fnv::new(),
106 audio_frames: 0,
107 }
108 }
109}
110
111fn guest_slice<'a>(mem: &'a [u8], ptr: u32, len: u64) -> wasmtime::Result<&'a [u8]> {
113 let start = ptr as u64;
114 let end = start.checked_add(len).ok_or_else(|| format_err!("guest pointer overflow"))?;
115 if end > mem.len() as u64 {
116 bail!("guest access out of bounds: {start:#x}..{end:#x} (memory is {:#x})", mem.len());
117 }
118 Ok(&mem[start as usize..end as usize])
119}
120
121fn memory(caller: &Caller<'_, Host>) -> wasmtime::Result<Memory> {
122 caller.data().memory.ok_or_else(|| format_err!("guest memory not yet available"))
123}
124
125fn guest_str(caller: &Caller<'_, Host>, ptr: u32, len: u32) -> wasmtime::Result<String> {
126 let mem = memory(caller)?;
127 let bytes = guest_slice(mem.data(caller), ptr, len as u64)?;
128 Ok(String::from_utf8_lossy(bytes).into_owned())
129}
130
131fn add_gasm_imports(linker: &mut Linker<Host>) -> wasmtime::Result<()> {
132 linker.func_wrap("gasm", "log", |caller: Caller<'_, Host>, ptr: u32, len: u32| {
133 let msg = guest_str(&caller, ptr, len)?;
134 eprintln!("[guest] {msg}");
135 Ok(())
136 })?;
137
138 linker.func_wrap("gasm", "time_ms", |caller: Caller<'_, Host>| -> f64 {
139 let h = caller.data();
140 h.virtual_time_ms.unwrap_or_else(|| h.start.elapsed().as_secs_f64() * 1000.0)
141 })?;
142
143 linker.func_wrap("gasm", "set_frame_rate", |mut caller: Caller<'_, Host>, hz: f64| {
144 if hz.is_finite() && (1.0..=1000.0).contains(&hz) {
145 caller.data_mut().frame_rate = hz;
146 }
147 })?;
148
149 linker.func_wrap(
150 "gasm",
151 "video_present",
152 |mut caller: Caller<'_, Host>, ptr: u32, w: u32, h: u32, stride: u32| -> wasmtime::Result<()> {
153 let (w, h, stride) = (w as usize, h as usize, stride as usize);
154 if w == 0 || h == 0 || w > 4096 || h > 4096 || stride < w * 4 {
155 bail!("video_present: bad geometry {w}x{h} stride {stride}");
156 }
157 let mem = memory(&caller)?;
158 let (data, host) = mem.data_and_store_mut(&mut caller);
159 let src = guest_slice(data, ptr, (stride * (h - 1) + w * 4) as u64)?;
160 host.rgba.resize(w * h * 4, 0);
161 for y in 0..h {
162 let row = &src[y * stride..y * stride + w * 4];
163 host.rgba[y * w * 4..(y + 1) * w * 4].copy_from_slice(row);
164 }
165 if host.hashing {
166 host.video_hash.update(&host.rgba);
167 }
168 host.width = w;
169 host.height = h;
170 host.frames_presented += 1;
171 Ok(())
172 },
173 )?;
174
175 linker.func_wrap("gasm", "audio_config", |mut caller: Caller<'_, Host>, rate: u32, channels: u32| {
176 let h = caller.data_mut();
177 if (8000..=192_000).contains(&rate) && (channels == 1 || channels == 2) {
178 h.audio_rate = rate;
179 h.audio_channels = channels;
180 if let Some(a) = &mut h.audio {
181 a.configure(rate, channels);
182 }
183 }
184 })?;
185
186 linker.func_wrap(
187 "gasm",
188 "audio_push",
189 |mut caller: Caller<'_, Host>, ptr: u32, frames: u32| -> wasmtime::Result<()> {
190 let mem = memory(&caller)?;
191 let (data, host) = mem.data_and_store_mut(&mut caller);
192 let len = frames as u64 * host.audio_channels as u64 * 4;
193 let bytes = guest_slice(data, ptr, len)?;
194 if host.hashing {
195 host.audio_hash.update(bytes);
196 }
197 host.audio_frames += frames as u64;
198 if let Some(a) = &mut host.audio {
199 a.push_le_f32(bytes);
200 }
201 Ok(())
202 },
203 )?;
204
205 linker.func_wrap("gasm", "input_pad", |caller: Caller<'_, Host>, player: u32| -> u32 {
206 caller.data().pads.get(player as usize).copied().unwrap_or(0)
207 })?;
208
209 linker.func_wrap(
210 "gasm",
211 "text_input",
212 |mut caller: Caller<'_, Host>, dst: u32, cap: u32| -> wasmtime::Result<i32> {
213 let mem = memory(&caller)?;
214 let (data, host) = mem.data_and_store_mut(&mut caller);
215 let Some(t) = &host.text else { return Ok(-1) };
216 let t = t.as_bytes();
217 if t.len() <= cap as usize {
218 guest_slice(data, dst, t.len() as u64)?;
219 data[dst as usize..dst as usize + t.len()].copy_from_slice(t);
220 }
221 Ok(t.len() as i32)
222 },
223 )?;
224
225 linker.func_wrap(
226 "gasm",
227 "param",
228 |mut caller: Caller<'_, Host>, ptr: u32, len: u32, dst: u32, cap: u32| -> wasmtime::Result<i32> {
229 let name = guest_str(&caller, ptr, len)?;
230 let mem = memory(&caller)?;
231 let (data, host) = mem.data_and_store_mut(&mut caller);
232 let Some(v) = host.params.get(&name) else { return Ok(-1) };
233 let v = v.as_bytes();
234 if v.len() <= cap as usize {
235 guest_slice(data, dst, v.len() as u64)?;
236 data[dst as usize..dst as usize + v.len()].copy_from_slice(v);
237 }
238 Ok(v.len() as i32)
239 },
240 )?;
241
242 linker.func_wrap(
243 "gasm",
244 "asset_read_at",
245 |mut caller: Caller<'_, Host>, ptr: u32, len: u32, offset: u32, dst: u32, cap: u32| -> wasmtime::Result<i32> {
246 let name = guest_str(&caller, ptr, len)?;
247 let mem = memory(&caller)?;
248 let (data, host) = mem.data_and_store_mut(&mut caller);
249 let Some(size) = host.assets.size(&name) else { return Ok(-1) };
250 let n = size.saturating_sub(offset as u64).min(cap as u64);
251 guest_slice(data, dst, n)?; let dst = &mut data[dst as usize..dst as usize + n as usize];
253 Ok(host.assets.read_at(&name, offset as u64, dst).map_or(-1, |k| k as i32))
254 },
255 )?;
256
257 linker.func_wrap("gasm", "asset_count", |mut caller: Caller<'_, Host>| -> u32 {
258 let h = caller.data_mut();
259 h.asset_names.get_or_insert_with(|| h.assets.names()).len() as u32
260 })?;
261
262 linker.func_wrap(
263 "gasm",
264 "asset_name",
265 |mut caller: Caller<'_, Host>, index: u32, dst: u32, cap: u32| -> wasmtime::Result<i32> {
266 let mem = memory(&caller)?;
267 let (data, host) = mem.data_and_store_mut(&mut caller);
268 let names = host.asset_names.get_or_insert_with(|| host.assets.names());
269 let Some(n) = names.get(index as usize) else { return Ok(-1) };
270 let n = n.as_bytes();
271 if n.len() <= cap as usize {
272 guest_slice(data, dst, n.len() as u64)?;
273 data[dst as usize..dst as usize + n.len()].copy_from_slice(n);
274 }
275 Ok(n.len() as i32)
276 },
277 )?;
278
279 linker.func_wrap(
280 "gasm",
281 "asset_size",
282 |caller: Caller<'_, Host>, ptr: u32, len: u32| -> wasmtime::Result<i32> {
283 let name = guest_str(&caller, ptr, len)?;
284 Ok(caller.data().assets.size(&name).map_or(-1, |n| n as i32))
285 },
286 )?;
287
288 linker.func_wrap(
289 "gasm",
290 "asset_read",
291 |mut caller: Caller<'_, Host>, ptr: u32, len: u32, dst: u32, cap: u32| -> wasmtime::Result<i32> {
292 let name = guest_str(&caller, ptr, len)?;
293 let mem = memory(&caller)?;
294 let (data, host) = mem.data_and_store_mut(&mut caller);
295 let Some(size) = host.assets.size(&name) else { return Ok(-1) };
296 let n = size.min(cap as u64);
297 guest_slice(data, dst, n)?;
298 let dst = &mut data[dst as usize..dst as usize + n as usize];
299 Ok(host.assets.read_at(&name, 0, dst).map_or(-1, |k| k as i32))
300 },
301 )?;
302 Ok(())
303}
304
305pub fn precompile(wasm: &[u8]) -> wasmtime::Result<Vec<u8>> {
307 Engine::default().precompile_module(wasm)
308}
309
310fn trap(e: String) -> wasmtime::Error {
311 format_err!("{e}")
312}
313
314fn add_gfx_imports(linker: &mut Linker<Host>) -> wasmtime::Result<()> {
315 const M: &str = "gasm:gfx";
316 linker.func_wrap(M, "width", |c: Caller<'_, Host>| c.data().gfx.size().0)?;
317 linker.func_wrap(M, "height", |c: Caller<'_, Host>| c.data().gfx.size().1)?;
318 linker.func_wrap(M, "create_shader", |mut c: Caller<'_, Host>, ptr: u32, len: u32| -> wasmtime::Result<u32> {
319 let src = guest_str(&c, ptr, len)?;
320 c.data_mut().gfx.create_shader(&src).map_err(trap)
321 })?;
322 linker.func_wrap(M, "create_buffer", |mut c: Caller<'_, Host>, size: u32, usage: u32| -> wasmtime::Result<u32> {
323 c.data_mut().gfx.create_buffer(size, usage).map_err(trap)
324 })?;
325 linker.func_wrap(M, "create_pipeline", |mut c: Caller<'_, Host>, ptr: u32, len: u32| -> wasmtime::Result<u32> {
326 let json = guest_str(&c, ptr, len)?;
327 c.data_mut().gfx.create_pipeline(&json).map_err(trap)
328 })?;
329 linker.func_wrap(M, "create_bind_group", |mut c: Caller<'_, Host>, ptr: u32, len: u32| -> wasmtime::Result<u32> {
330 let json = guest_str(&c, ptr, len)?;
331 c.data_mut().gfx.create_bind_group(&json).map_err(trap)
332 })?;
333 linker.func_wrap(
334 M,
335 "write_buffer",
336 |mut c: Caller<'_, Host>, buf: u32, offset: u32, ptr: u32, len: u32| -> wasmtime::Result<()> {
337 let mem = memory(&c)?;
338 let (data, host) = mem.data_and_store_mut(&mut c);
339 let bytes = guest_slice(data, ptr, len as u64)?;
340 if host.hashing {
341 host.video_hash.update(bytes);
342 }
343 host.gfx.write_buffer(buf, offset, bytes).map_err(trap)
344 },
345 )?;
346 linker.func_wrap(M, "create_bind_group_layout", |mut c: Caller<'_, Host>, ptr: u32, len: u32| -> wasmtime::Result<u32> {
347 let json = guest_str(&c, ptr, len)?;
348 c.data_mut().gfx.create_bind_group_layout(&json).map_err(trap)
349 })?;
350 linker.func_wrap(M, "create_texture", |mut c: Caller<'_, Host>, ptr: u32, len: u32| -> wasmtime::Result<u32> {
351 let json = guest_str(&c, ptr, len)?;
352 c.data_mut().gfx.create_texture(&json).map_err(trap)
353 })?;
354 linker.func_wrap(M, "create_sampler", |mut c: Caller<'_, Host>, ptr: u32, len: u32| -> wasmtime::Result<u32> {
355 let json = guest_str(&c, ptr, len)?;
356 c.data_mut().gfx.create_sampler(&json).map_err(trap)
357 })?;
358 linker.func_wrap(
359 M,
360 "write_texture",
361 |mut c: Caller<'_, Host>, tex: u32, mip: u32, x: u32, y: u32, w: u32, h: u32, ptr: u32, len: u32| -> wasmtime::Result<()> {
362 let mem = memory(&c)?;
363 let (data, host) = mem.data_and_store_mut(&mut c);
364 let bytes = guest_slice(data, ptr, len as u64)?;
365 host.gfx.write_texture(tex, mip, x, y, w, h, bytes).map_err(trap)?;
366 if host.hashing {
367 for v in [tex, mip, x, y, w, h] {
369 host.video_hash.update(&v.to_le_bytes());
370 }
371 host.video_hash.update(bytes);
372 }
373 Ok(())
374 },
375 )?;
376 linker.func_wrap(M, "begin_frame", |mut c: Caller<'_, Host>, r: f32, g: f32, b: f32, a: f32| -> wasmtime::Result<u32> {
377 let h = c.data_mut();
378 let show = h.show_frame;
379 h.gfx.begin_frame([r, g, b, a], show).map(u32::from).map_err(trap)
380 })?;
381 linker.func_wrap(M, "set_pipeline", |mut c: Caller<'_, Host>, p: u32| -> wasmtime::Result<()> {
382 c.data_mut().gfx.set_pipeline(p).map_err(trap)
383 })?;
384 linker.func_wrap(M, "set_bind_group", |mut c: Caller<'_, Host>, i: u32, bg: u32| -> wasmtime::Result<()> {
385 c.data_mut().gfx.set_bind_group(i, bg).map_err(trap)
386 })?;
387 linker.func_wrap(
388 M,
389 "set_bind_group_offsets",
390 |mut c: Caller<'_, Host>, i: u32, bg: u32, ptr: u32, count: u32| -> wasmtime::Result<()> {
391 let mem = memory(&c)?;
392 let (data, host) = mem.data_and_store_mut(&mut c);
393 let bytes = guest_slice(data, ptr, count as u64 * 4)?;
394 let offsets: Vec<u32> = bytes.chunks_exact(4).map(|b| u32::from_le_bytes([b[0], b[1], b[2], b[3]])).collect();
395 host.gfx.set_bind_group_offsets(i, bg, &offsets).map_err(trap)
396 },
397 )?;
398 linker.func_wrap(
399 M,
400 "set_viewport",
401 |mut c: Caller<'_, Host>, x: f32, y: f32, w: f32, h: f32, min: f32, max: f32| -> wasmtime::Result<()> {
402 c.data_mut().gfx.set_viewport(x, y, w, h, min, max).map_err(trap)
403 },
404 )?;
405 linker.func_wrap(M, "set_scissor_rect", |mut c: Caller<'_, Host>, x: u32, y: u32, w: u32, h: u32| -> wasmtime::Result<()> {
406 c.data_mut().gfx.set_scissor_rect(x, y, w, h).map_err(trap)
407 })?;
408 linker.func_wrap(M, "set_vertex_buffer", |mut c: Caller<'_, Host>, slot: u32, b: u32, off: u32| -> wasmtime::Result<()> {
409 c.data_mut().gfx.set_vertex_buffer(slot, b, off).map_err(trap)
410 })?;
411 linker.func_wrap(M, "set_index_buffer", |mut c: Caller<'_, Host>, b: u32, fmt: u32, off: u32| -> wasmtime::Result<()> {
412 c.data_mut().gfx.set_index_buffer(b, fmt, off).map_err(trap)
413 })?;
414 linker.func_wrap(M, "draw", |mut c: Caller<'_, Host>, vc: u32, ic: u32, fv: u32, fi: u32| {
415 c.data_mut().gfx.draw(vc, ic, fv, fi)
416 })?;
417 linker.func_wrap(M, "draw_indexed", |mut c: Caller<'_, Host>, ic: u32, n: u32, first: u32, base: i32, fi: u32| {
418 c.data_mut().gfx.draw_indexed(ic, n, first, base, fi)
419 })?;
420 linker.func_wrap(M, "end_frame", |mut c: Caller<'_, Host>| -> wasmtime::Result<()> {
421 let h = c.data_mut();
422 h.frames_presented += 1;
423 h.gfx.end_frame().map_err(trap)
424 })?;
425 Ok(())
426}
427
428fn add_net_imports(linker: &mut Linker<Host>) -> wasmtime::Result<()> {
429 const M: &str = "gasm:net";
430 linker.func_wrap(M, "open", |mut c: Caller<'_, Host>, ptr: u32, len: u32| -> wasmtime::Result<i32> {
431 let url = guest_str(&c, ptr, len)?;
432 Ok(c.data_mut().net.open(&url))
433 })?;
434 linker.func_wrap(M, "state", |c: Caller<'_, Host>, h: i32| c.data().net.state(h))?;
435 linker.func_wrap(M, "send", |mut c: Caller<'_, Host>, h: i32, ptr: u32, len: u32| -> wasmtime::Result<i32> {
436 if len == 0 {
437 return Ok(-1);
438 }
439 let mem = memory(&c)?;
440 let msg = guest_slice(mem.data(&c), ptr, len as u64)?.to_vec();
441 Ok(c.data_mut().net.send(h, msg))
442 })?;
443 linker.func_wrap(M, "recv", |mut c: Caller<'_, Host>, h: i32, dst: u32, cap: u32| -> wasmtime::Result<i32> {
444 let mem = memory(&c)?;
445 let (data, host) = mem.data_and_store_mut(&mut c);
446 let msg = match host.net.peek(h) {
447 Ok(m) => m,
448 Err(code) => return Ok(code),
449 };
450 let n = msg.len();
451 if n <= cap as usize {
452 guest_slice(data, dst, n as u64)?;
453 data[dst as usize..dst as usize + n].copy_from_slice(msg);
454 host.net.pop(h);
455 }
456 Ok(n as i32)
457 })?;
458 linker.func_wrap(M, "close", |mut c: Caller<'_, Host>, h: i32| c.data_mut().net.close(h))?;
459 Ok(())
460}
461
462fn add_storage_imports(linker: &mut Linker<Host>) -> wasmtime::Result<()> {
463 const M: &str = "gasm:storage";
464 linker.func_wrap(M, "get", |mut c: Caller<'_, Host>, kp: u32, kl: u32, dst: u32, cap: u32| -> wasmtime::Result<i32> {
465 let key = guest_str(&c, kp, kl)?;
466 let mem = memory(&c)?;
467 let (data, host) = mem.data_and_store_mut(&mut c);
468 let Some(v) = host.storage.get(&key) else { return Ok(-1) };
469 if v.len() <= cap as usize {
470 guest_slice(data, dst, v.len() as u64)?;
471 data[dst as usize..dst as usize + v.len()].copy_from_slice(v);
472 }
473 Ok(v.len() as i32)
474 })?;
475 linker.func_wrap(M, "set", |mut c: Caller<'_, Host>, kp: u32, kl: u32, vp: u32, vl: u32| -> wasmtime::Result<i32> {
476 let key = guest_str(&c, kp, kl)?;
477 let mem = memory(&c)?;
478 let (data, host) = mem.data_and_store_mut(&mut c);
479 let value = guest_slice(data, vp, vl as u64)?;
480 Ok(match host.storage.set(&key, value) {
481 Ok(()) => 0,
482 Err(e) => {
483 eprintln!("[gasm] storage: {e}");
484 -1
485 }
486 })
487 })?;
488 linker.func_wrap(M, "delete", |mut c: Caller<'_, Host>, kp: u32, kl: u32| -> wasmtime::Result<i32> {
489 let key = guest_str(&c, kp, kl)?;
490 Ok(if c.data_mut().storage.delete(&key) { 0 } else { -1 })
491 })?;
492 Ok(())
493}
494
495#[derive(Debug)]
497pub enum Stop {
498 Exit(i32),
500 Trap(String),
501}
502
503impl std::fmt::Display for Stop {
504 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
505 match self {
506 Stop::Exit(code) => write!(f, "guest exited with code {code}"),
507 Stop::Trap(msg) => write!(f, "guest trapped: {msg}"),
508 }
509 }
510}
511
512impl std::error::Error for Stop {}
513
514fn classify(e: wasmtime::Error) -> Stop {
515 match e.downcast_ref::<wasmtime_wasi::I32Exit>() {
516 Some(exit) => Stop::Exit(exit.0),
517 None => Stop::Trap(format!("{e:?}")),
518 }
519}
520
521pub struct Game {
522 pub store: Store<Host>,
523 frame: TypedFunc<(), ()>,
524 exit: Option<TypedFunc<(), ()>>,
525}
526
527impl Game {
528 pub fn load(wasm: &[u8], host: Host) -> Result<Game, Stop> {
529 Self::load_inner(wasm, host).map_err(classify)
530 }
531
532 fn load_inner(wasm: &[u8], host: Host) -> wasmtime::Result<Game> {
533 let engine = Engine::default();
534 let module = if wasm.starts_with(b"\0asm") {
535 Module::new(&engine, wasm)?
536 } else {
537 unsafe { Module::deserialize(&engine, wasm)? }
540 };
541 let mut linker: Linker<Host> = Linker::new(&engine);
542 p1::add_to_linker_sync(&mut linker, |h: &mut Host| &mut h.wasi)?;
543 add_gasm_imports(&mut linker)?;
544 add_gfx_imports(&mut linker)?;
545 add_net_imports(&mut linker)?;
546 add_storage_imports(&mut linker)?;
547 linker.define_unknown_imports_as_traps(&module)?;
550
551 let mut store = Store::new(&engine, host);
552 let instance: Instance = linker.instantiate(&mut store, &module)?;
553 let memory = instance
554 .get_memory(&mut store, "memory")
555 .ok_or_else(|| format_err!("guest does not export `memory`"))?;
556 store.data_mut().memory = Some(memory);
557
558 if let Ok(init) = instance.get_typed_func::<(), ()>(&mut store, "_initialize") {
559 init.call(&mut store, ())?;
560 }
561 let version = instance
562 .get_typed_func::<(), i32>(&mut store, "gasm_abi_version")?
563 .call(&mut store, ())?;
564 if version != ABI_VERSION {
565 bail!("guest targets gasm ABI v{version}, runner implements v{ABI_VERSION}");
566 }
567 let rc = instance.get_typed_func::<(), i32>(&mut store, "gasm_init")?.call(&mut store, ())?;
568 if rc != 0 {
569 bail!("gasm_init failed with code {rc}");
570 }
571 let frame = instance.get_typed_func::<(), ()>(&mut store, "gasm_frame")?;
572 let exit = instance.get_typed_func::<(), ()>(&mut store, "gasm_exit").ok();
573 Ok(Game { store, frame, exit })
574 }
575
576 pub fn frame(&mut self) -> Result<(), Stop> {
577 self.frame.call(&mut self.store, ()).map_err(classify)
578 }
579
580 pub fn exit(&mut self) {
582 if let Some(f) = self.exit.take() {
583 if let Err(e) = f.call(&mut self.store, ()) {
584 if let Stop::Trap(t) = classify(e) {
585 eprintln!("[gasm] gasm_exit trapped: {t}");
586 }
587 }
588 }
589 }
590
591 pub fn host(&self) -> &Host {
592 self.store.data()
593 }
594
595 pub fn host_mut(&mut self) -> &mut Host {
596 self.store.data_mut()
597 }
598}