1pub mod sys;
17
18#[cfg(not(target_arch = "wasm32"))]
19pub mod native;
20
21use std::cell::UnsafeCell;
22
23pub const ABI_VERSION: i32 = 0;
24
25pub fn log(msg: &str) {
28 unsafe { sys::log(msg.as_ptr(), msg.len() as u32) }
29}
30
31#[macro_export]
33macro_rules! log {
34 ($($t:tt)*) => { $crate::log(&::std::format!($($t)*)) };
35}
36
37pub fn time_ms() -> f64 {
39 unsafe { sys::time_ms() }
40}
41
42pub fn has(name: &str) -> bool {
46 unsafe { sys::has(name.as_ptr(), name.len() as u32) == 1 }
47}
48
49pub fn set_frame_rate(hz: f64) {
51 unsafe { sys::set_frame_rate(hz) }
52}
53
54pub fn video_set_aspect(num: u32, den: u32) -> bool {
59 static SUPPORTED: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
60 let ok = *SUPPORTED.get_or_init(|| has("gasm.video_set_aspect"));
61 if ok {
62 unsafe { sys::video_set_aspect(num, den) }
63 }
64 ok
65}
66
67#[macro_export]
71macro_rules! title {
72 ($t:literal) => {
73 #[cfg_attr(target_arch = "wasm32", unsafe(link_section = "gasm.title"))]
75 #[used]
76 static __GASM_TITLE: [u8; $t.len()] = $crate::__title_bytes($t);
77 };
78}
79
80#[doc(hidden)]
81pub const fn __title_bytes<const N: usize>(s: &str) -> [u8; N] {
82 let b = s.as_bytes();
83 let mut out = [0u8; N];
84 let mut i = 0;
85 while i < N {
86 out[i] = b[i];
87 i += 1;
88 }
89 out
90}
91
92pub fn set_title(title: &str) {
97 static SUPPORTED: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
98 if *SUPPORTED.get_or_init(|| has("gasm.set_title")) {
99 unsafe { sys::set_title(title.as_ptr(), title.len() as u32) }
100 }
101}
102
103pub fn param(name: &str) -> Option<String> {
105 let len = unsafe { sys::param(name.as_ptr(), name.len() as u32, std::ptr::null_mut(), 0) };
106 if len < 0 {
107 return None;
108 }
109 let mut buf = vec![0u8; len as usize];
110 unsafe { sys::param(name.as_ptr(), name.len() as u32, buf.as_mut_ptr(), len as u32) };
111 Some(String::from_utf8_lossy(&buf).into_owned())
112}
113
114pub fn exit(code: i32) -> ! {
116 unsafe { sys::proc_exit(code) }
117}
118
119pub fn present(rgba: &[u8], width: u32, height: u32, stride: u32) {
123 let need = (stride as u64)
124 .checked_mul(height.saturating_sub(1) as u64)
125 .and_then(|n| n.checked_add(width as u64 * 4))
126 .filter(|_| width > 0 && height > 0 && stride as u64 >= width as u64 * 4);
127 assert!(need.is_some_and(|n| rgba.len() as u64 >= n), "present: bad geometry or buffer too small");
128 unsafe { sys::video_present(rgba.as_ptr(), width, height, stride) }
129}
130
131pub mod audio {
132 use std::sync::atomic::{AtomicU32, Ordering};
133 static CHANNELS: AtomicU32 = AtomicU32::new(2);
134
135 pub fn config(sample_rate: u32, channels: u32) {
138 if (8000..=192_000).contains(&sample_rate) && (channels == 1 || channels == 2) {
139 CHANNELS.store(channels, Ordering::Relaxed);
140 }
141 unsafe { crate::sys::audio_config(sample_rate, channels) }
142 }
143
144 pub fn push(samples: &[f32]) {
147 let ch = CHANNELS.load(Ordering::Relaxed);
148 debug_assert!(samples.len() % ch as usize == 0, "audio::push: not a whole number of frames");
149 unsafe { crate::sys::audio_push(samples.as_ptr(), samples.len() as u32 / ch) }
150 }
151}
152
153#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
155pub struct Buttons(pub u32);
156
157impl Buttons {
158 pub const A: u32 = sys::GASM_BTN_A;
159 pub const B: u32 = sys::GASM_BTN_B;
160 pub const X: u32 = sys::GASM_BTN_X;
161 pub const Y: u32 = sys::GASM_BTN_Y;
162 pub const L: u32 = sys::GASM_BTN_L;
163 pub const R: u32 = sys::GASM_BTN_R;
164 pub const SELECT: u32 = sys::GASM_BTN_SELECT;
165 pub const START: u32 = sys::GASM_BTN_START;
166 pub const UP: u32 = sys::GASM_BTN_UP;
167 pub const DOWN: u32 = sys::GASM_BTN_DOWN;
168 pub const LEFT: u32 = sys::GASM_BTN_LEFT;
169 pub const RIGHT: u32 = sys::GASM_BTN_RIGHT;
170
171 pub fn held(self, mask: u32) -> bool {
172 self.0 & mask != 0
173 }
174}
175
176pub fn pad(player: u32) -> Buttons {
178 Buttons(unsafe { sys::input_pad(player) })
179}
180
181pub fn text_input() -> Option<String> {
184 let len = unsafe { sys::text_input(std::ptr::null_mut(), 0) };
185 if len < 0 {
186 return None;
187 }
188 let mut buf = vec![0u8; len as usize];
189 if len > 0 {
190 unsafe { sys::text_input(buf.as_mut_ptr(), len as u32) };
191 }
192 Some(String::from_utf8_lossy(&buf).into_owned())
193}
194
195pub fn asset_names() -> Vec<String> {
197 let n = unsafe { sys::asset_count() };
198 (0..n)
199 .filter_map(|i| {
200 let len = unsafe { sys::asset_name(i, std::ptr::null_mut(), 0) };
201 if len < 0 {
202 return None;
203 }
204 let mut buf = vec![0u8; len as usize];
205 if len > 0 {
206 unsafe { sys::asset_name(i, buf.as_mut_ptr(), len as u32) };
207 }
208 Some(String::from_utf8_lossy(&buf).into_owned())
209 })
210 .collect()
211}
212
213pub fn asset_size(name: &str) -> Option<u64> {
215 let n = unsafe { sys::asset_size64(name.as_ptr(), name.len() as u32) };
216 (n >= 0).then_some(n as u64)
217}
218
219pub fn asset(name: &str) -> Option<Vec<u8>> {
221 let n = unsafe { sys::asset_size(name.as_ptr(), name.len() as u32) };
222 if n < 0 {
223 return None;
224 }
225 let mut buf = vec![0u8; n as usize];
226 let got = unsafe { sys::asset_read(name.as_ptr(), name.len() as u32, buf.as_mut_ptr(), n as u32) };
227 buf.truncate(got.max(0) as usize);
228 Some(buf)
229}
230
231pub fn asset_read_at(name: &str, offset: u64, buf: &mut [u8]) -> Option<usize> {
234 let len = buf.len().min(u32::MAX as usize) as u32;
235 let n = unsafe { sys::asset_read_at64(name.as_ptr(), name.len() as u32, offset, buf.as_mut_ptr(), len) };
236 (n >= 0).then_some(n as usize)
237}
238
239pub mod storage {
246 use crate::sys;
247
248 pub fn get(key: &str) -> Option<Vec<u8>> {
249 let n = unsafe { sys::storage_get(key.as_ptr(), key.len() as u32, std::ptr::null_mut(), 0) };
250 if n < 0 {
251 return None;
252 }
253 let mut buf = vec![0u8; n as usize];
254 unsafe { sys::storage_get(key.as_ptr(), key.len() as u32, buf.as_mut_ptr(), n as u32) };
255 Some(buf)
256 }
257
258 #[derive(Clone, Copy, Debug, PartialEq, Eq)]
260 pub enum Error {
261 Key,
263 Size,
265 Quota,
267 Io,
269 }
270
271 pub fn try_set(key: &str, value: &[u8]) -> Result<(), Error> {
273 match unsafe { sys::storage_set(key.as_ptr(), key.len() as u32, value.as_ptr(), value.len() as u32) } {
274 sys::GASM_STORAGE_OK => Ok(()),
275 sys::GASM_STORAGE_ERR_KEY => Err(Error::Key),
276 sys::GASM_STORAGE_ERR_SIZE => Err(Error::Size),
277 sys::GASM_STORAGE_ERR_QUOTA => Err(Error::Quota),
278 _ => Err(Error::Io),
279 }
280 }
281
282 pub fn set(key: &str, value: &[u8]) -> bool {
284 try_set(key, value).is_ok()
285 }
286
287 pub fn delete(key: &str) -> bool {
289 unsafe { sys::storage_delete(key.as_ptr(), key.len() as u32) == 0 }
290 }
291
292 pub fn keys() -> Vec<String> {
294 let n = unsafe { sys::storage_count() };
295 (0..n)
296 .filter_map(|i| {
297 let len = unsafe { sys::storage_key(i, std::ptr::null_mut(), 0) };
298 if len < 0 {
299 return None;
300 }
301 let mut buf = vec![0u8; len as usize];
302 unsafe { sys::storage_key(i, buf.as_mut_ptr(), len as u32) };
303 Some(String::from_utf8_lossy(&buf).into_owned())
304 })
305 .collect()
306 }
307}
308
309pub mod input {
321 use crate::sys;
322
323 pub const KEYS_RAW: u32 = sys::GASM_INPUT_KEYS_RAW;
325 pub const POINTER_HIDDEN: u32 = sys::GASM_INPUT_POINTER_HIDDEN;
327 pub const POINTER_LOCKED: u32 = sys::GASM_INPUT_POINTER_LOCKED;
329
330 pub const MOUSE_LEFT: u32 = sys::GASM_MOUSE_LEFT;
331 pub const MOUSE_RIGHT: u32 = sys::GASM_MOUSE_RIGHT;
332 pub const MOUSE_MIDDLE: u32 = sys::GASM_MOUSE_MIDDLE;
333 pub const MOUSE_BACK: u32 = sys::GASM_MOUSE_BACK;
334 pub const MOUSE_FORWARD: u32 = sys::GASM_MOUSE_FORWARD;
335
336 const KEY_BYTES: usize = sys::GASM_KEY_STATE_BYTES as usize;
337 const POINTER_BYTES: usize = sys::GASM_POINTER_BYTES as usize;
338 const GAMEPAD_BYTES: usize = sys::GASM_GAMEPAD_BYTES as usize;
339
340 pub fn set_mode(flags: u32) {
342 unsafe { sys::input_mode(flags) }
343 }
344
345 #[derive(Clone, Copy, Default, PartialEq, Eq)]
347 pub struct Keys(pub [u8; KEY_BYTES]);
348
349 impl Keys {
350 pub fn held(&self, code: u32) -> bool {
351 (code as usize) < KEY_BYTES * 8 && self.0[code as usize / 8] & (1 << (code % 8)) != 0
352 }
353 }
354
355 pub fn keys() -> Option<Keys> {
357 let mut k = Keys::default();
358 (unsafe { sys::key_state(k.0.as_mut_ptr(), KEY_BYTES as u32) } >= 0).then_some(k)
359 }
360
361 #[derive(Clone, Copy, Debug, PartialEq, Eq)]
362 pub struct KeyEvent {
363 pub code: u32,
364 pub down: bool,
365 }
366
367 pub fn key_events() -> Option<Vec<KeyEvent>> {
369 let n = unsafe { sys::key_events(std::ptr::null_mut(), 0) };
370 if n < 0 {
371 return None;
372 }
373 let mut b = vec![0u8; n as usize];
374 if n > 0 {
375 unsafe { sys::key_events(b.as_mut_ptr(), n as u32) };
376 }
377 Some(b.chunks_exact(sys::GASM_KEY_EVENT_BYTES as usize).map(|e| KeyEvent { code: u16::from_le_bytes([e[0], e[1]]) as u32, down: e[2] != 0 }).collect())
378 }
379
380 #[derive(Clone, Copy, Debug, Default, PartialEq)]
381 pub struct Pointer {
382 pub x: f32,
384 pub y: f32,
385 pub frame_x: f32,
387 pub frame_y: f32,
388 pub dx: f32,
390 pub dy: f32,
391 pub wheel_x: f32,
393 pub wheel_y: f32,
394 pub buttons: u32,
395 pub pressed: u32,
397 pub released: u32,
398 pub flags: u32,
399 }
400
401 impl Pointer {
402 pub fn inside(&self) -> bool {
403 self.flags & 1 != 0
404 }
405 pub fn locked(&self) -> bool {
406 self.flags & 4 != 0
407 }
408 }
409
410 pub fn pointer() -> Option<Pointer> {
412 let mut b = [0u8; POINTER_BYTES];
413 if unsafe { sys::pointer(b.as_mut_ptr(), POINTER_BYTES as u32) } < 0 {
414 return None;
415 }
416 let f = |o: u32| { let i = o as usize; f32::from_le_bytes([b[i], b[i + 1], b[i + 2], b[i + 3]]) };
417 let u = |o: u32| { let i = o as usize; u32::from_le_bytes([b[i], b[i + 1], b[i + 2], b[i + 3]]) };
418 Some(Pointer {
419 x: f(sys::GASM_POINTER_OFF_X), y: f(sys::GASM_POINTER_OFF_Y),
420 frame_x: f(sys::GASM_POINTER_OFF_FX), frame_y: f(sys::GASM_POINTER_OFF_FY),
421 dx: f(sys::GASM_POINTER_OFF_DX), dy: f(sys::GASM_POINTER_OFF_DY),
422 wheel_x: f(sys::GASM_POINTER_OFF_WHEEL_X), wheel_y: f(sys::GASM_POINTER_OFF_WHEEL_Y),
423 buttons: u(sys::GASM_POINTER_OFF_BUTTONS), pressed: u(sys::GASM_POINTER_OFF_PRESSED),
424 released: u(sys::GASM_POINTER_OFF_RELEASED), flags: u(sys::GASM_POINTER_OFF_FLAGS),
425 })
426 }
427
428 #[derive(Clone, Debug, Default, PartialEq)]
432 pub struct Gamepad {
433 pub connected: bool,
434 pub standard: bool,
435 pub buttons: Vec<f32>,
436 pub axes: Vec<f32>,
437 }
438
439 pub fn gamepad(slot: u32) -> Option<Gamepad> {
441 let mut b = [0u8; GAMEPAD_BYTES];
442 if unsafe { sys::gamepad(slot, b.as_mut_ptr(), GAMEPAD_BYTES as u32) } < 0 {
443 return None;
444 }
445 let u = |i: usize| u32::from_le_bytes([b[i], b[i + 1], b[i + 2], b[i + 3]]);
446 let f = |i: usize| f32::from_le_bytes([b[i], b[i + 1], b[i + 2], b[i + 3]]);
447 let (flags, nb, na) = (
448 u(sys::GASM_GAMEPAD_OFF_FLAGS as usize),
449 (u(sys::GASM_GAMEPAD_OFF_BUTTON_COUNT as usize) as usize).min(sys::GASM_GAMEPAD_BUTTONS as usize),
450 (u(sys::GASM_GAMEPAD_OFF_AXIS_COUNT as usize) as usize).min(sys::GASM_GAMEPAD_AXES as usize),
451 );
452 Some(Gamepad {
453 connected: flags & sys::GASM_GAMEPAD_CONNECTED != 0,
454 standard: flags & sys::GASM_GAMEPAD_STANDARD != 0,
455 buttons: (0..nb).map(|i| f(sys::GASM_GAMEPAD_OFF_BUTTONS as usize + i * 4)).collect(),
456 axes: (0..na).map(|i| f(sys::GASM_GAMEPAD_OFF_AXES as usize + i * 4)).collect(),
457 })
458 }
459
460 pub fn gamepad_name(slot: u32) -> Option<String> {
462 let n = unsafe { sys::gamepad_name(slot, std::ptr::null_mut(), 0) };
463 if n < 0 {
464 return None;
465 }
466 let mut b = vec![0u8; n as usize];
467 if n > 0 {
468 unsafe { sys::gamepad_name(slot, b.as_mut_ptr(), n as u32) };
469 }
470 Some(String::from_utf8_lossy(&b).into_owned())
471 }
472}
473
474pub use sys::keys;
476
477pub mod gfx {
481 use crate::sys;
482
483 #[derive(Clone, Copy, Debug, PartialEq, Eq)]
484 pub struct Shader(pub u32);
485 #[derive(Clone, Copy, Debug, PartialEq, Eq)]
486 pub struct Buffer(pub u32);
487 #[derive(Clone, Copy, Debug, PartialEq, Eq)]
488 pub struct Pipeline(pub u32);
489 #[derive(Clone, Copy, Debug, PartialEq, Eq)]
490 pub struct BindGroup(pub u32);
491 #[derive(Clone, Copy, Debug, PartialEq, Eq)]
492 pub struct BindGroupLayout(pub u32);
493 #[derive(Clone, Copy, Debug, PartialEq, Eq)]
494 pub struct Texture(pub u32);
495 #[derive(Clone, Copy, Debug, PartialEq, Eq)]
496 pub struct Sampler(pub u32);
497
498 pub const COPY_DST: u32 = sys::GASM_BUF_COPY_DST;
499 pub const INDEX: u32 = sys::GASM_BUF_INDEX;
500 pub const VERTEX: u32 = sys::GASM_BUF_VERTEX;
501 pub const UNIFORM: u32 = sys::GASM_BUF_UNIFORM;
502 pub const STORAGE: u32 = sys::GASM_BUF_STORAGE;
503
504 pub const STAGE_VERTEX: u32 = sys::GASM_STAGE_VERTEX;
506 pub const STAGE_FRAGMENT: u32 = sys::GASM_STAGE_FRAGMENT;
507 pub const OFFSET_ALIGNMENT: u32 = 256;
509
510 #[derive(Clone, Copy)]
511 pub enum IndexFormat {
512 U16 = sys::GASM_INDEX_U16 as isize,
513 U32 = sys::GASM_INDEX_U32 as isize,
514 }
515
516 pub trait Handle {
518 fn raw(&self) -> u32;
519 }
520 macro_rules! handles {
521 ($($t:ident),*) => { $(impl Handle for $t { fn raw(&self) -> u32 { self.0 } })* };
522 }
523 handles!(Shader, Buffer, Pipeline, BindGroup, BindGroupLayout, Texture, Sampler);
524
525 pub unsafe trait Pod: Copy {}
530 unsafe impl Pod for u8 {}
531 unsafe impl Pod for u16 {}
532 unsafe impl Pod for u32 {}
533 unsafe impl Pod for f32 {}
534 unsafe impl<T: Pod, const N: usize> Pod for [T; N] {}
535
536 pub fn as_bytes<T: Pod>(data: &[T]) -> &[u8] {
537 unsafe { std::slice::from_raw_parts(data.as_ptr() as *const u8, std::mem::size_of_val(data)) }
538 }
539
540 pub fn size() -> (u32, u32) {
542 unsafe { (sys::gfx_width(), sys::gfx_height()) }
543 }
544 pub fn create_shader(wgsl: &str) -> Shader {
545 Shader(unsafe { sys::gfx_create_shader(wgsl.as_ptr(), wgsl.len() as u32) })
546 }
547 pub fn create_buffer(size: u32, usage: u32) -> Buffer {
548 Buffer(unsafe { sys::gfx_create_buffer(size, usage) })
549 }
550 pub fn create_pipeline(json: &str) -> Pipeline {
553 Pipeline(unsafe { sys::gfx_create_pipeline(json.as_ptr(), json.len() as u32) })
554 }
555 pub fn create_bind_group(json: &str) -> BindGroup {
558 BindGroup(unsafe { sys::gfx_create_bind_group(json.as_ptr(), json.len() as u32) })
559 }
560 pub fn create_bind_group_layout(json: &str) -> BindGroupLayout {
563 BindGroupLayout(unsafe { sys::gfx_create_bind_group_layout(json.as_ptr(), json.len() as u32) })
564 }
565 pub fn create_texture(json: &str) -> Texture {
567 Texture(unsafe { sys::gfx_create_texture(json.as_ptr(), json.len() as u32) })
568 }
569 pub fn write_texture(t: Texture, mip: u32, x: u32, y: u32, w: u32, h: u32, rgba: &[u8]) {
571 unsafe { sys::gfx_write_texture(t.0, mip, x, y, w, h, rgba.as_ptr(), rgba.len() as u32) }
572 }
573 pub fn create_sampler(json: &str) -> Sampler {
575 Sampler(unsafe { sys::gfx_create_sampler(json.as_ptr(), json.len() as u32) })
576 }
577 pub fn write_buffer<T: Pod>(buf: Buffer, offset: u32, data: &[T]) {
579 let b = as_bytes(data);
580 unsafe { sys::gfx_write_buffer(buf.0, offset, b.as_ptr(), b.len() as u32) }
581 }
582 pub fn begin_frame(clear: [f32; 4]) -> bool {
584 unsafe { sys::gfx_begin_frame(clear[0], clear[1], clear[2], clear[3]) != 0 }
585 }
586 pub fn set_pipeline(p: Pipeline) {
587 unsafe { sys::gfx_set_pipeline(p.0) }
588 }
589 pub fn set_bind_group(index: u32, bg: BindGroup) {
590 unsafe { sys::gfx_set_bind_group(index, bg.0) }
591 }
592 pub fn set_bind_group_offsets(index: u32, bg: BindGroup, offsets: &[u32]) {
594 unsafe { sys::gfx_set_bind_group_offsets(index, bg.0, offsets.as_ptr(), offsets.len() as u32) }
595 }
596 pub fn set_viewport(x: f32, y: f32, w: f32, h: f32, min_depth: f32, max_depth: f32) {
598 unsafe { sys::gfx_set_viewport(x, y, w, h, min_depth, max_depth) }
599 }
600 pub fn set_scissor_rect(x: u32, y: u32, w: u32, h: u32) {
602 unsafe { sys::gfx_set_scissor_rect(x, y, w, h) }
603 }
604 pub fn set_vertex_buffer(slot: u32, buf: Buffer, offset: u32) {
605 unsafe { sys::gfx_set_vertex_buffer(slot, buf.0, offset) }
606 }
607 pub fn set_index_buffer(buf: Buffer, format: IndexFormat, offset: u32) {
608 unsafe { sys::gfx_set_index_buffer(buf.0, format as u32, offset) }
609 }
610 pub fn draw(vertices: u32, instances: u32, first_vertex: u32, first_instance: u32) {
611 unsafe { sys::gfx_draw(vertices, instances, first_vertex, first_instance) }
612 }
613 pub fn draw_indexed(indices: u32, instances: u32, first_index: u32, base_vertex: i32, first_instance: u32) {
614 unsafe { sys::gfx_draw_indexed(indices, instances, first_index, base_vertex, first_instance) }
615 }
616 pub fn end_frame() {
617 unsafe { sys::gfx_end_frame() }
618 }
619 pub fn destroy(h: impl Handle) {
622 unsafe { sys::gfx_destroy(h.raw()) }
623 }
624}
625
626pub mod net {
630 use crate::sys;
631
632 #[derive(Clone, Copy, Debug, PartialEq, Eq)]
633 pub enum State {
634 Connecting,
635 Open,
636 Closed,
637 Error,
638 }
639
640 pub enum Recv {
641 Message(Vec<u8>),
642 Empty,
643 Closed,
644 }
645
646 #[derive(Debug)]
647 pub struct Conn(i32);
648
649 impl Conn {
650 pub fn open(url: &str) -> Option<Conn> {
652 let h = unsafe { sys::net_open(url.as_ptr(), url.len() as u32) };
653 (h > 0).then_some(Conn(h))
654 }
655 pub fn state(&self) -> State {
656 match unsafe { sys::net_state(self.0) } {
657 0 => State::Connecting,
658 1 => State::Open,
659 2 => State::Closed,
660 _ => State::Error,
661 }
662 }
663 pub fn send(&self, msg: &[u8]) -> bool {
665 !msg.is_empty() && unsafe { sys::net_send(self.0, msg.as_ptr(), msg.len() as u32) } == 0
666 }
667 pub fn recv(&self) -> Recv {
669 let mut buf = [0u8; 1024];
670 let n = unsafe { sys::net_recv(self.0, buf.as_mut_ptr(), buf.len() as u32) };
671 match n {
672 0 => Recv::Empty,
673 n if n < 0 => Recv::Closed,
674 n if n as usize <= buf.len() => Recv::Message(buf[..n as usize].to_vec()),
675 n => {
676 let mut big = vec![0u8; n as usize];
677 unsafe { sys::net_recv(self.0, big.as_mut_ptr(), n as u32) };
678 Recv::Message(big)
679 }
680 }
681 }
682 }
683
684 impl Drop for Conn {
685 fn drop(&mut self) {
686 unsafe { sys::net_close(self.0) }
687 }
688 }
689}
690
691pub trait Game: Sized + 'static {
695 fn init() -> Result<Self, String>;
696 fn frame(&mut self);
697 fn exit(&mut self) {}
700}
701
702#[doc(hidden)]
703pub struct GameCell<T>(pub UnsafeCell<Option<T>>);
704unsafe impl<T: Send> Sync for GameCell<T> {}
707
708#[doc(hidden)]
711pub unsafe fn __init<G: Game>(cell: &GameCell<G>) -> i32 {
712 std::panic::set_hook(Box::new(|info| log(&format!("panic: {info}"))));
713 match G::init() {
714 Ok(g) => {
715 unsafe { *cell.0.get() = Some(g) };
716 0
717 }
718 Err(e) => {
719 log(&format!("init failed: {e}"));
720 1
721 }
722 }
723}
724
725#[doc(hidden)]
728pub unsafe fn __frame<G: Game>(cell: &GameCell<G>) {
729 if let Some(g) = unsafe { (*cell.0.get()).as_mut() } {
730 g.frame();
731 }
732}
733
734#[doc(hidden)]
737pub unsafe fn __exit<G: Game>(cell: &GameCell<G>) {
738 if let Some(g) = unsafe { (*cell.0.get()).as_mut() } {
739 g.exit();
740 }
741}
742
743#[macro_export]
745macro_rules! game {
746 ($t:ty) => {
747 static __GASM_GAME: $crate::GameCell<$t> = $crate::GameCell(::std::cell::UnsafeCell::new(None));
748
749 #[unsafe(no_mangle)]
750 pub extern "C" fn gasm_abi_version() -> i32 {
751 $crate::ABI_VERSION
752 }
753 #[unsafe(no_mangle)]
754 pub extern "C" fn gasm_init() -> i32 {
755 unsafe { $crate::__init::<$t>(&__GASM_GAME) }
756 }
757 #[unsafe(no_mangle)]
758 pub extern "C" fn gasm_frame() {
759 unsafe { $crate::__frame::<$t>(&__GASM_GAME) }
760 }
761 #[unsafe(no_mangle)]
762 pub extern "C" fn gasm_exit() {
763 unsafe { $crate::__exit::<$t>(&__GASM_GAME) }
764 }
765 };
766}
767
768pub mod main_loop {
794 #![cfg_attr(not(target_arch = "wasm32"), allow(dead_code))]
796
797 #[cfg(target_arch = "wasm32")]
798 mod asyncify {
799 #[link(wasm_import_module = "asyncify")]
800 unsafe extern "C" {
801 pub fn start_unwind(data: *mut u8);
802 pub fn stop_unwind();
803 pub fn start_rewind(data: *mut u8);
804 pub fn stop_rewind();
805 }
806 }
807
808 const STACK: usize = 1024 * 1024;
809
810 #[repr(C)]
811 struct Data {
812 cur: *mut u8,
813 end: *mut u8,
814 }
815
816 static mut DATA: Data = Data { cur: std::ptr::null_mut(), end: std::ptr::null_mut() };
818 static mut STACK_BUF: [u8; STACK] = [0; STACK];
819 static mut REWINDING: bool = false;
820 static mut UNWINDING: bool = false;
821 static mut STARTED: bool = false;
822 static mut FINISHED: bool = false;
823 static mut EXIT_CODE: i32 = 0;
824 static mut MAIN: Option<fn() -> i32> = None;
825 static mut FRAMES: u32 = 0;
826
827 fn wait_impl() {
828 #[cfg(target_arch = "wasm32")]
829 unsafe {
830 if REWINDING {
831 asyncify::stop_rewind();
832 REWINDING = false;
833 FRAMES += 1;
834 return;
835 }
836 DATA.cur = (&raw mut STACK_BUF).cast::<u8>();
837 DATA.end = DATA.cur.add(STACK);
838 UNWINDING = true;
839 asyncify::start_unwind((&raw mut DATA).cast::<u8>());
840 }
841 }
842
843 static mut WAIT: fn() = wait_impl;
846
847 pub fn wait_frame() {
849 let f = unsafe { std::ptr::read_volatile(&raw const WAIT) };
850 f()
851 }
852
853 pub fn frames() -> u32 {
855 unsafe { FRAMES }
856 }
857
858 #[doc(hidden)]
859 pub fn set_main(f: fn() -> i32) {
860 unsafe { MAIN = Some(f) }
861 }
862
863 #[inline(never)]
866 fn run_main() {
867 unsafe {
868 let rc = MAIN.map_or(0, |f| f());
869 if !UNWINDING {
870 EXIT_CODE = rc;
871 FINISHED = true;
872 }
873 }
874 }
875
876 #[doc(hidden)]
881 #[inline(always)]
882 pub fn __loop_frame() {
883 unsafe {
884 if FINISHED {
885 return;
886 }
887 #[cfg(target_arch = "wasm32")]
888 {
889 if !STARTED {
890 STARTED = true;
891 } else {
892 REWINDING = true;
893 asyncify::start_rewind((&raw mut DATA).cast::<u8>());
894 }
895 run_main();
896 if UNWINDING {
897 asyncify::stop_unwind();
898 UNWINDING = false;
899 return;
900 }
901 }
902 #[cfg(not(target_arch = "wasm32"))]
903 {
904 STARTED = true;
905 run_main();
906 }
907 if FINISHED {
908 crate::exit(EXIT_CODE);
909 }
910 }
911 }
912}
913
914#[macro_export]
916macro_rules! main_loop {
917 ($main:path) => {
918 #[unsafe(no_mangle)]
919 pub extern "C" fn gasm_abi_version() -> i32 {
920 $crate::ABI_VERSION
921 }
922 #[unsafe(no_mangle)]
923 pub extern "C" fn gasm_init() -> i32 {
924 $crate::main_loop::set_main($main);
925 0
926 }
927 #[unsafe(no_mangle)]
928 pub extern "C" fn gasm_frame() {
929 gasm_loop_frame()
930 }
931 #[unsafe(no_mangle)]
932 #[inline(never)]
933 pub extern "C" fn gasm_loop_frame() {
934 $crate::main_loop::__loop_frame()
935 }
936 };
937 ($main:path, $exit:path) => {
938 $crate::main_loop!($main);
939 #[unsafe(no_mangle)]
940 pub extern "C" fn gasm_exit() {
941 $exit()
942 }
943 };
944}