1pub mod sys;
17
18pub mod gles;
19pub mod sync;
20pub mod thread;
21
22#[cfg(not(target_arch = "wasm32"))]
23pub mod native;
24
25use std::cell::UnsafeCell;
26
27pub const ABI_VERSION: i32 = 0;
28
29pub fn log(msg: &str) {
32 unsafe { sys::log(msg.as_ptr(), msg.len() as u32) }
33}
34
35#[macro_export]
37macro_rules! log {
38 ($($t:tt)*) => { $crate::log(&::std::format!($($t)*)) };
39}
40
41pub fn time_ms() -> f64 {
43 unsafe { sys::time_ms() }
44}
45
46pub fn has(name: &str) -> bool {
50 unsafe { sys::has(name.as_ptr(), name.len() as u32) == 1 }
51}
52
53pub fn set_frame_rate(hz: f64) {
55 unsafe { sys::set_frame_rate(hz) }
56}
57
58pub fn video_set_aspect(num: u32, den: u32) -> bool {
63 static SUPPORTED: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
64 let ok = *SUPPORTED.get_or_init(|| has("gasm.video_set_aspect"));
65 if ok {
66 unsafe { sys::video_set_aspect(num, den) }
67 }
68 ok
69}
70
71#[macro_export]
75macro_rules! title {
76 ($t:literal) => {
77 #[cfg_attr(target_arch = "wasm32", unsafe(link_section = "gasm.title"))]
79 #[used]
80 static __GASM_TITLE: [u8; $t.len()] = $crate::__title_bytes($t);
81 };
82}
83
84#[doc(hidden)]
85pub const fn __title_bytes<const N: usize>(s: &str) -> [u8; N] {
86 let b = s.as_bytes();
87 let mut out = [0u8; N];
88 let mut i = 0;
89 while i < N {
90 out[i] = b[i];
91 i += 1;
92 }
93 out
94}
95
96pub fn set_title(title: &str) {
101 static SUPPORTED: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
102 if *SUPPORTED.get_or_init(|| has("gasm.set_title")) {
103 unsafe { sys::set_title(title.as_ptr(), title.len() as u32) }
104 }
105}
106
107pub fn param(name: &str) -> Option<String> {
109 let len = unsafe { sys::param(name.as_ptr(), name.len() as u32, std::ptr::null_mut(), 0) };
110 if len < 0 {
111 return None;
112 }
113 let mut buf = vec![0u8; len as usize];
114 unsafe { sys::param(name.as_ptr(), name.len() as u32, buf.as_mut_ptr(), len as u32) };
115 Some(String::from_utf8_lossy(&buf).into_owned())
116}
117
118pub fn exit(code: i32) -> ! {
120 unsafe { sys::proc_exit(code) }
121}
122
123pub fn present(rgba: &[u8], width: u32, height: u32, stride: u32) {
127 let need = (stride as u64)
128 .checked_mul(height.saturating_sub(1) as u64)
129 .and_then(|n| n.checked_add(width as u64 * 4))
130 .filter(|_| width > 0 && height > 0 && stride as u64 >= width as u64 * 4);
131 assert!(need.is_some_and(|n| rgba.len() as u64 >= n), "present: bad geometry or buffer too small");
132 unsafe { sys::video_present(rgba.as_ptr(), width, height, stride) }
133}
134
135pub mod audio {
136 use std::sync::atomic::{AtomicU32, Ordering};
137 static CHANNELS: AtomicU32 = AtomicU32::new(2);
138
139 pub fn config(sample_rate: u32, channels: u32) {
142 if (8000..=192_000).contains(&sample_rate) && (channels == 1 || channels == 2) {
143 CHANNELS.store(channels, Ordering::Relaxed);
144 }
145 unsafe { crate::sys::audio_config(sample_rate, channels) }
146 }
147
148 pub fn push(samples: &[f32]) {
151 let ch = CHANNELS.load(Ordering::Relaxed);
152 debug_assert!(samples.len() % ch as usize == 0, "audio::push: not a whole number of frames");
153 unsafe { crate::sys::audio_push(samples.as_ptr(), samples.len() as u32 / ch) }
154 }
155}
156
157#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
159pub struct Buttons(pub u32);
160
161impl Buttons {
162 pub const A: u32 = sys::GASM_BTN_A;
163 pub const B: u32 = sys::GASM_BTN_B;
164 pub const X: u32 = sys::GASM_BTN_X;
165 pub const Y: u32 = sys::GASM_BTN_Y;
166 pub const L: u32 = sys::GASM_BTN_L;
167 pub const R: u32 = sys::GASM_BTN_R;
168 pub const SELECT: u32 = sys::GASM_BTN_SELECT;
169 pub const START: u32 = sys::GASM_BTN_START;
170 pub const UP: u32 = sys::GASM_BTN_UP;
171 pub const DOWN: u32 = sys::GASM_BTN_DOWN;
172 pub const LEFT: u32 = sys::GASM_BTN_LEFT;
173 pub const RIGHT: u32 = sys::GASM_BTN_RIGHT;
174
175 pub fn held(self, mask: u32) -> bool {
176 self.0 & mask != 0
177 }
178}
179
180pub fn pad(player: u32) -> Buttons {
182 Buttons(unsafe { sys::input_pad(player) })
183}
184
185pub fn text_input() -> Option<String> {
188 let len = unsafe { sys::text_input(std::ptr::null_mut(), 0) };
189 if len < 0 {
190 return None;
191 }
192 let mut buf = vec![0u8; len as usize];
193 if len > 0 {
194 unsafe { sys::text_input(buf.as_mut_ptr(), len as u32) };
195 }
196 Some(String::from_utf8_lossy(&buf).into_owned())
197}
198
199pub fn asset_names() -> Vec<String> {
201 let n = unsafe { sys::asset_count() };
202 (0..n)
203 .filter_map(|i| {
204 let len = unsafe { sys::asset_name(i, std::ptr::null_mut(), 0) };
205 if len < 0 {
206 return None;
207 }
208 let mut buf = vec![0u8; len as usize];
209 if len > 0 {
210 unsafe { sys::asset_name(i, buf.as_mut_ptr(), len as u32) };
211 }
212 Some(String::from_utf8_lossy(&buf).into_owned())
213 })
214 .collect()
215}
216
217pub fn asset_size(name: &str) -> Option<u64> {
219 let n = unsafe { sys::asset_size64(name.as_ptr(), name.len() as u32) };
220 (n >= 0).then_some(n as u64)
221}
222
223pub fn asset(name: &str) -> Option<Vec<u8>> {
225 let n = unsafe { sys::asset_size(name.as_ptr(), name.len() as u32) };
226 if n < 0 {
227 return None;
228 }
229 let mut buf = vec![0u8; n as usize];
230 let got = unsafe { sys::asset_read(name.as_ptr(), name.len() as u32, buf.as_mut_ptr(), n as u32) };
231 buf.truncate(got.max(0) as usize);
232 Some(buf)
233}
234
235pub fn asset_read_at(name: &str, offset: u64, buf: &mut [u8]) -> Option<usize> {
238 let len = buf.len().min(u32::MAX as usize) as u32;
239 let n = unsafe { sys::asset_read_at64(name.as_ptr(), name.len() as u32, offset, buf.as_mut_ptr(), len) };
240 (n >= 0).then_some(n as usize)
241}
242
243pub mod storage {
250 use crate::sys;
251
252 pub fn get(key: &str) -> Option<Vec<u8>> {
253 let n = unsafe { sys::storage_get(key.as_ptr(), key.len() as u32, std::ptr::null_mut(), 0) };
254 if n < 0 {
255 return None;
256 }
257 let mut buf = vec![0u8; n as usize];
258 unsafe { sys::storage_get(key.as_ptr(), key.len() as u32, buf.as_mut_ptr(), n as u32) };
259 Some(buf)
260 }
261
262 #[derive(Clone, Copy, Debug, PartialEq, Eq)]
264 pub enum Error {
265 Key,
267 Size,
269 Quota,
271 Io,
273 }
274
275 pub fn try_set(key: &str, value: &[u8]) -> Result<(), Error> {
277 match unsafe { sys::storage_set(key.as_ptr(), key.len() as u32, value.as_ptr(), value.len() as u32) } {
278 sys::GASM_STORAGE_OK => Ok(()),
279 sys::GASM_STORAGE_ERR_KEY => Err(Error::Key),
280 sys::GASM_STORAGE_ERR_SIZE => Err(Error::Size),
281 sys::GASM_STORAGE_ERR_QUOTA => Err(Error::Quota),
282 _ => Err(Error::Io),
283 }
284 }
285
286 pub fn set(key: &str, value: &[u8]) -> bool {
288 try_set(key, value).is_ok()
289 }
290
291 pub fn delete(key: &str) -> bool {
293 unsafe { sys::storage_delete(key.as_ptr(), key.len() as u32) == 0 }
294 }
295
296 pub fn keys() -> Vec<String> {
298 let n = unsafe { sys::storage_count() };
299 (0..n)
300 .filter_map(|i| {
301 let len = unsafe { sys::storage_key(i, std::ptr::null_mut(), 0) };
302 if len < 0 {
303 return None;
304 }
305 let mut buf = vec![0u8; len as usize];
306 unsafe { sys::storage_key(i, buf.as_mut_ptr(), len as u32) };
307 Some(String::from_utf8_lossy(&buf).into_owned())
308 })
309 .collect()
310 }
311}
312
313pub mod input {
325 use crate::sys;
326
327 pub const KEYS_RAW: u32 = sys::GASM_INPUT_KEYS_RAW;
329 pub const POINTER_HIDDEN: u32 = sys::GASM_INPUT_POINTER_HIDDEN;
331 pub const POINTER_LOCKED: u32 = sys::GASM_INPUT_POINTER_LOCKED;
333
334 pub const MOUSE_LEFT: u32 = sys::GASM_MOUSE_LEFT;
335 pub const MOUSE_RIGHT: u32 = sys::GASM_MOUSE_RIGHT;
336 pub const MOUSE_MIDDLE: u32 = sys::GASM_MOUSE_MIDDLE;
337 pub const MOUSE_BACK: u32 = sys::GASM_MOUSE_BACK;
338 pub const MOUSE_FORWARD: u32 = sys::GASM_MOUSE_FORWARD;
339
340 const KEY_BYTES: usize = sys::GASM_KEY_STATE_BYTES as usize;
341 const POINTER_BYTES: usize = sys::GASM_POINTER_BYTES as usize;
342 const GAMEPAD_BYTES: usize = sys::GASM_GAMEPAD_BYTES as usize;
343
344 pub fn set_mode(flags: u32) {
346 unsafe { sys::input_mode(flags) }
347 }
348
349 #[derive(Clone, Copy, Default, PartialEq, Eq)]
351 pub struct Keys(pub [u8; KEY_BYTES]);
352
353 impl Keys {
354 pub fn held(&self, code: u32) -> bool {
355 (code as usize) < KEY_BYTES * 8 && self.0[code as usize / 8] & (1 << (code % 8)) != 0
356 }
357 }
358
359 pub fn keys() -> Option<Keys> {
361 let mut k = Keys::default();
362 (unsafe { sys::key_state(k.0.as_mut_ptr(), KEY_BYTES as u32) } >= 0).then_some(k)
363 }
364
365 #[derive(Clone, Copy, Debug, PartialEq, Eq)]
366 pub struct KeyEvent {
367 pub code: u32,
368 pub down: bool,
369 }
370
371 pub fn key_events() -> Option<Vec<KeyEvent>> {
373 let n = unsafe { sys::key_events(std::ptr::null_mut(), 0) };
374 if n < 0 {
375 return None;
376 }
377 let mut b = vec![0u8; n as usize];
378 if n > 0 {
379 unsafe { sys::key_events(b.as_mut_ptr(), n as u32) };
380 }
381 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())
382 }
383
384 #[derive(Clone, Copy, Debug, Default, PartialEq)]
385 pub struct Pointer {
386 pub x: f32,
388 pub y: f32,
389 pub frame_x: f32,
391 pub frame_y: f32,
392 pub dx: f32,
394 pub dy: f32,
395 pub wheel_x: f32,
397 pub wheel_y: f32,
398 pub buttons: u32,
399 pub pressed: u32,
401 pub released: u32,
402 pub flags: u32,
403 }
404
405 impl Pointer {
406 pub fn inside(&self) -> bool {
407 self.flags & 1 != 0
408 }
409 pub fn locked(&self) -> bool {
410 self.flags & 4 != 0
411 }
412 }
413
414 pub fn pointer() -> Option<Pointer> {
416 let mut b = [0u8; POINTER_BYTES];
417 if unsafe { sys::pointer(b.as_mut_ptr(), POINTER_BYTES as u32) } < 0 {
418 return None;
419 }
420 let f = |o: u32| { let i = o as usize; f32::from_le_bytes([b[i], b[i + 1], b[i + 2], b[i + 3]]) };
421 let u = |o: u32| { let i = o as usize; u32::from_le_bytes([b[i], b[i + 1], b[i + 2], b[i + 3]]) };
422 Some(Pointer {
423 x: f(sys::GASM_POINTER_OFF_X), y: f(sys::GASM_POINTER_OFF_Y),
424 frame_x: f(sys::GASM_POINTER_OFF_FX), frame_y: f(sys::GASM_POINTER_OFF_FY),
425 dx: f(sys::GASM_POINTER_OFF_DX), dy: f(sys::GASM_POINTER_OFF_DY),
426 wheel_x: f(sys::GASM_POINTER_OFF_WHEEL_X), wheel_y: f(sys::GASM_POINTER_OFF_WHEEL_Y),
427 buttons: u(sys::GASM_POINTER_OFF_BUTTONS), pressed: u(sys::GASM_POINTER_OFF_PRESSED),
428 released: u(sys::GASM_POINTER_OFF_RELEASED), flags: u(sys::GASM_POINTER_OFF_FLAGS),
429 })
430 }
431
432 #[derive(Clone, Debug, Default, PartialEq)]
436 pub struct Gamepad {
437 pub connected: bool,
438 pub standard: bool,
439 pub buttons: Vec<f32>,
440 pub axes: Vec<f32>,
441 }
442
443 pub fn gamepad(slot: u32) -> Option<Gamepad> {
445 let mut b = [0u8; GAMEPAD_BYTES];
446 if unsafe { sys::gamepad(slot, b.as_mut_ptr(), GAMEPAD_BYTES as u32) } < 0 {
447 return None;
448 }
449 let u = |i: usize| u32::from_le_bytes([b[i], b[i + 1], b[i + 2], b[i + 3]]);
450 let f = |i: usize| f32::from_le_bytes([b[i], b[i + 1], b[i + 2], b[i + 3]]);
451 let (flags, nb, na) = (
452 u(sys::GASM_GAMEPAD_OFF_FLAGS as usize),
453 (u(sys::GASM_GAMEPAD_OFF_BUTTON_COUNT as usize) as usize).min(sys::GASM_GAMEPAD_BUTTONS as usize),
454 (u(sys::GASM_GAMEPAD_OFF_AXIS_COUNT as usize) as usize).min(sys::GASM_GAMEPAD_AXES as usize),
455 );
456 Some(Gamepad {
457 connected: flags & sys::GASM_GAMEPAD_CONNECTED != 0,
458 standard: flags & sys::GASM_GAMEPAD_STANDARD != 0,
459 buttons: (0..nb).map(|i| f(sys::GASM_GAMEPAD_OFF_BUTTONS as usize + i * 4)).collect(),
460 axes: (0..na).map(|i| f(sys::GASM_GAMEPAD_OFF_AXES as usize + i * 4)).collect(),
461 })
462 }
463
464 pub fn gamepad_name(slot: u32) -> Option<String> {
466 let n = unsafe { sys::gamepad_name(slot, std::ptr::null_mut(), 0) };
467 if n < 0 {
468 return None;
469 }
470 let mut b = vec![0u8; n as usize];
471 if n > 0 {
472 unsafe { sys::gamepad_name(slot, b.as_mut_ptr(), n as u32) };
473 }
474 Some(String::from_utf8_lossy(&b).into_owned())
475 }
476}
477
478pub use sys::keys;
480
481pub mod gfx {
485 use crate::sys;
486
487 #[derive(Clone, Copy, Debug, PartialEq, Eq)]
488 pub struct Shader(pub u32);
489 #[derive(Clone, Copy, Debug, PartialEq, Eq)]
490 pub struct Buffer(pub u32);
491 #[derive(Clone, Copy, Debug, PartialEq, Eq)]
492 pub struct Pipeline(pub u32);
493 #[derive(Clone, Copy, Debug, PartialEq, Eq)]
494 pub struct BindGroup(pub u32);
495 #[derive(Clone, Copy, Debug, PartialEq, Eq)]
496 pub struct BindGroupLayout(pub u32);
497 #[derive(Clone, Copy, Debug, PartialEq, Eq)]
498 pub struct Texture(pub u32);
499 #[derive(Clone, Copy, Debug, PartialEq, Eq)]
500 pub struct Sampler(pub u32);
501
502 pub const COPY_DST: u32 = sys::GASM_BUF_COPY_DST;
503 pub const INDEX: u32 = sys::GASM_BUF_INDEX;
504 pub const VERTEX: u32 = sys::GASM_BUF_VERTEX;
505 pub const UNIFORM: u32 = sys::GASM_BUF_UNIFORM;
506 pub const STORAGE: u32 = sys::GASM_BUF_STORAGE;
507
508 pub const STAGE_VERTEX: u32 = sys::GASM_STAGE_VERTEX;
510 pub const STAGE_FRAGMENT: u32 = sys::GASM_STAGE_FRAGMENT;
511 pub const OFFSET_ALIGNMENT: u32 = 256;
513
514 #[derive(Clone, Copy)]
515 pub enum IndexFormat {
516 U16 = sys::GASM_INDEX_U16 as isize,
517 U32 = sys::GASM_INDEX_U32 as isize,
518 }
519
520 pub trait Handle {
522 fn raw(&self) -> u32;
523 }
524 macro_rules! handles {
525 ($($t:ident),*) => { $(impl Handle for $t { fn raw(&self) -> u32 { self.0 } })* };
526 }
527 handles!(Shader, Buffer, Pipeline, BindGroup, BindGroupLayout, Texture, Sampler);
528
529 pub unsafe trait Pod: Copy {}
534 unsafe impl Pod for u8 {}
535 unsafe impl Pod for u16 {}
536 unsafe impl Pod for u32 {}
537 unsafe impl Pod for f32 {}
538 unsafe impl<T: Pod, const N: usize> Pod for [T; N] {}
539
540 pub fn as_bytes<T: Pod>(data: &[T]) -> &[u8] {
541 unsafe { std::slice::from_raw_parts(data.as_ptr() as *const u8, std::mem::size_of_val(data)) }
542 }
543
544 pub fn size() -> (u32, u32) {
546 unsafe { (sys::gfx_width(), sys::gfx_height()) }
547 }
548 pub fn create_shader(wgsl: &str) -> Shader {
549 Shader(unsafe { sys::gfx_create_shader(wgsl.as_ptr(), wgsl.len() as u32) })
550 }
551 pub fn create_buffer(size: u32, usage: u32) -> Buffer {
552 Buffer(unsafe { sys::gfx_create_buffer(size, usage) })
553 }
554 pub fn create_pipeline(json: &str) -> Pipeline {
557 Pipeline(unsafe { sys::gfx_create_pipeline(json.as_ptr(), json.len() as u32) })
558 }
559 pub fn create_bind_group(json: &str) -> BindGroup {
562 BindGroup(unsafe { sys::gfx_create_bind_group(json.as_ptr(), json.len() as u32) })
563 }
564 pub fn create_bind_group_layout(json: &str) -> BindGroupLayout {
567 BindGroupLayout(unsafe { sys::gfx_create_bind_group_layout(json.as_ptr(), json.len() as u32) })
568 }
569 pub fn create_texture(json: &str) -> Texture {
571 Texture(unsafe { sys::gfx_create_texture(json.as_ptr(), json.len() as u32) })
572 }
573 pub fn write_texture(t: Texture, mip: u32, x: u32, y: u32, w: u32, h: u32, rgba: &[u8]) {
575 unsafe { sys::gfx_write_texture(t.0, mip, x, y, w, h, rgba.as_ptr(), rgba.len() as u32) }
576 }
577 pub fn create_sampler(json: &str) -> Sampler {
579 Sampler(unsafe { sys::gfx_create_sampler(json.as_ptr(), json.len() as u32) })
580 }
581 pub fn write_buffer<T: Pod>(buf: Buffer, offset: u32, data: &[T]) {
583 let b = as_bytes(data);
584 unsafe { sys::gfx_write_buffer(buf.0, offset, b.as_ptr(), b.len() as u32) }
585 }
586 pub fn begin_frame(clear: [f32; 4]) -> bool {
588 unsafe { sys::gfx_begin_frame(clear[0], clear[1], clear[2], clear[3]) != 0 }
589 }
590 pub fn set_pipeline(p: Pipeline) {
591 unsafe { sys::gfx_set_pipeline(p.0) }
592 }
593 pub fn set_bind_group(index: u32, bg: BindGroup) {
594 unsafe { sys::gfx_set_bind_group(index, bg.0) }
595 }
596 pub fn set_bind_group_offsets(index: u32, bg: BindGroup, offsets: &[u32]) {
598 unsafe { sys::gfx_set_bind_group_offsets(index, bg.0, offsets.as_ptr(), offsets.len() as u32) }
599 }
600 pub fn set_viewport(x: f32, y: f32, w: f32, h: f32, min_depth: f32, max_depth: f32) {
602 unsafe { sys::gfx_set_viewport(x, y, w, h, min_depth, max_depth) }
603 }
604 pub fn set_scissor_rect(x: u32, y: u32, w: u32, h: u32) {
606 unsafe { sys::gfx_set_scissor_rect(x, y, w, h) }
607 }
608 pub fn set_vertex_buffer(slot: u32, buf: Buffer, offset: u32) {
609 unsafe { sys::gfx_set_vertex_buffer(slot, buf.0, offset) }
610 }
611 pub fn set_index_buffer(buf: Buffer, format: IndexFormat, offset: u32) {
612 unsafe { sys::gfx_set_index_buffer(buf.0, format as u32, offset) }
613 }
614 pub fn draw(vertices: u32, instances: u32, first_vertex: u32, first_instance: u32) {
615 unsafe { sys::gfx_draw(vertices, instances, first_vertex, first_instance) }
616 }
617 pub fn draw_indexed(indices: u32, instances: u32, first_index: u32, base_vertex: i32, first_instance: u32) {
618 unsafe { sys::gfx_draw_indexed(indices, instances, first_index, base_vertex, first_instance) }
619 }
620 pub fn end_frame() {
621 unsafe { sys::gfx_end_frame() }
622 }
623 pub fn destroy(h: impl Handle) {
626 unsafe { sys::gfx_destroy(h.raw()) }
627 }
628}
629
630pub mod net {
634 use crate::sys;
635
636 #[derive(Clone, Copy, Debug, PartialEq, Eq)]
637 pub enum State {
638 Connecting,
639 Open,
640 Closed,
641 Error,
642 }
643
644 pub enum Recv {
645 Message(Vec<u8>),
646 Empty,
647 Closed,
648 }
649
650 #[derive(Debug)]
651 pub struct Conn(i32);
652
653 impl Conn {
654 pub fn open(url: &str) -> Option<Conn> {
656 let h = unsafe { sys::net_open(url.as_ptr(), url.len() as u32) };
657 (h > 0).then_some(Conn(h))
658 }
659 pub fn state(&self) -> State {
660 match unsafe { sys::net_state(self.0) } {
661 0 => State::Connecting,
662 1 => State::Open,
663 2 => State::Closed,
664 _ => State::Error,
665 }
666 }
667 pub fn send(&self, msg: &[u8]) -> bool {
669 !msg.is_empty() && unsafe { sys::net_send(self.0, msg.as_ptr(), msg.len() as u32) } == 0
670 }
671 pub fn recv(&self) -> Recv {
673 let mut buf = [0u8; 1024];
674 let n = unsafe { sys::net_recv(self.0, buf.as_mut_ptr(), buf.len() as u32) };
675 match n {
676 0 => Recv::Empty,
677 n if n < 0 => Recv::Closed,
678 n if n as usize <= buf.len() => Recv::Message(buf[..n as usize].to_vec()),
679 n => {
680 let mut big = vec![0u8; n as usize];
681 unsafe { sys::net_recv(self.0, big.as_mut_ptr(), n as u32) };
682 Recv::Message(big)
683 }
684 }
685 }
686 }
687
688 impl Drop for Conn {
689 fn drop(&mut self) {
690 unsafe { sys::net_close(self.0) }
691 }
692 }
693}
694
695pub trait Game: Sized + 'static {
699 fn init() -> Result<Self, String>;
700 fn frame(&mut self);
701 fn exit(&mut self) {}
704}
705
706#[doc(hidden)]
707pub struct GameCell<T>(pub UnsafeCell<Option<T>>);
708#[cfg(target_arch = "wasm32")]
712unsafe impl<T> Sync for GameCell<T> {}
713#[cfg(not(target_arch = "wasm32"))]
714unsafe impl<T: Send> Sync for GameCell<T> {}
715
716#[doc(hidden)]
719pub unsafe fn __init<G: Game>(cell: &GameCell<G>) -> i32 {
720 std::panic::set_hook(Box::new(|info| log(&format!("panic: {info}"))));
721 match G::init() {
722 Ok(g) => {
723 unsafe { *cell.0.get() = Some(g) };
724 0
725 }
726 Err(e) => {
727 log(&format!("init failed: {e}"));
728 1
729 }
730 }
731}
732
733#[doc(hidden)]
736pub unsafe fn __frame<G: Game>(cell: &GameCell<G>) {
737 if let Some(g) = unsafe { (*cell.0.get()).as_mut() } {
738 g.frame();
739 }
740}
741
742#[doc(hidden)]
745pub unsafe fn __exit<G: Game>(cell: &GameCell<G>) {
746 if let Some(g) = unsafe { (*cell.0.get()).as_mut() } {
747 g.exit();
748 }
749}
750
751#[macro_export]
753macro_rules! game {
754 ($t:ty) => {
755 static __GASM_GAME: $crate::GameCell<$t> = $crate::GameCell(::std::cell::UnsafeCell::new(None));
756
757 #[unsafe(no_mangle)]
758 pub extern "C" fn gasm_abi_version() -> i32 {
759 $crate::ABI_VERSION
760 }
761 #[unsafe(no_mangle)]
762 pub extern "C" fn gasm_init() -> i32 {
763 unsafe { $crate::__init::<$t>(&__GASM_GAME) }
764 }
765 #[unsafe(no_mangle)]
766 pub extern "C" fn gasm_frame() {
767 unsafe { $crate::__frame::<$t>(&__GASM_GAME) }
768 }
769 #[unsafe(no_mangle)]
770 pub extern "C" fn gasm_exit() {
771 unsafe { $crate::__exit::<$t>(&__GASM_GAME) }
772 }
773 };
774}
775
776pub mod main_loop {
804 #![cfg_attr(not(target_arch = "wasm32"), allow(dead_code))]
806
807 #[cfg(target_arch = "wasm32")]
808 mod asyncify {
809 #[link(wasm_import_module = "asyncify")]
810 unsafe extern "C" {
811 pub fn start_unwind(data: *mut u8);
812 pub fn stop_unwind();
813 pub fn start_rewind(data: *mut u8);
814 pub fn stop_rewind();
815 }
816 }
817
818 const STACK: usize = 1024 * 1024;
819
820 #[repr(C)]
821 struct Data {
822 cur: *mut u8,
823 end: *mut u8,
824 }
825
826 static mut DATA: Data = Data { cur: std::ptr::null_mut(), end: std::ptr::null_mut() };
828 static mut STACK_BUF: [u8; STACK] = [0; STACK];
829 static mut REWINDING: bool = false;
830 static mut UNWINDING: bool = false;
831 static mut STARTED: bool = false;
832 static mut FINISHED: bool = false;
833 static mut EXIT_CODE: i32 = 0;
834 static mut MAIN: Option<fn() -> i32> = None;
835 static mut FRAMES: u32 = 0;
836 static mut RUN_MODE: bool = false;
838
839 fn wait_impl() {
840 #[cfg(target_arch = "wasm32")]
841 unsafe {
842 if RUN_MODE {
843 crate::sys::yield_frame();
844 FRAMES += 1;
845 return;
846 }
847 if REWINDING {
848 asyncify::stop_rewind();
849 REWINDING = false;
850 FRAMES += 1;
851 return;
852 }
853 DATA.cur = (&raw mut STACK_BUF).cast::<u8>();
854 DATA.end = DATA.cur.add(STACK);
855 UNWINDING = true;
856 asyncify::start_unwind((&raw mut DATA).cast::<u8>());
857 }
858 }
859
860 static mut WAIT: fn() = wait_impl;
863
864 pub fn wait_frame() {
866 if crate::thread::available() {
867 return crate::thread::wait_frame(); }
869 let f = unsafe { std::ptr::read_volatile(&raw const WAIT) };
870 f()
871 }
872
873 pub fn frames() -> u32 {
875 if crate::thread::available() {
876 return crate::thread::frames();
877 }
878 unsafe { FRAMES }
879 }
880
881 #[doc(hidden)]
882 pub fn set_main(f: fn() -> i32) {
883 unsafe { MAIN = Some(f) }
884 }
885
886 #[inline(never)]
889 fn run_main() {
890 unsafe {
891 let rc = MAIN.map_or(0, |f| f());
892 if !UNWINDING {
893 EXIT_CODE = rc;
894 FINISHED = true;
895 }
896 }
897 }
898
899 #[doc(hidden)]
902 pub fn __run() -> i32 {
903 unsafe {
904 RUN_MODE = true;
905 STARTED = true;
906 MAIN.map_or(0, |f| f())
907 }
908 }
909
910 #[doc(hidden)]
915 #[inline(always)]
916 pub fn __loop_frame() {
917 unsafe {
918 if FINISHED {
919 return;
920 }
921 #[cfg(target_arch = "wasm32")]
922 {
923 if !STARTED {
924 STARTED = true;
925 } else {
926 REWINDING = true;
927 asyncify::start_rewind((&raw mut DATA).cast::<u8>());
928 }
929 run_main();
930 if UNWINDING {
931 asyncify::stop_unwind();
932 UNWINDING = false;
933 return;
934 }
935 }
936 #[cfg(not(target_arch = "wasm32"))]
937 {
938 STARTED = true;
939 run_main();
940 }
941 if FINISHED {
942 crate::exit(EXIT_CODE);
943 }
944 }
945 }
946}
947
948#[macro_export]
953macro_rules! threaded_main_loop {
954 ($main:path) => {
955 #[unsafe(no_mangle)]
956 pub extern "C" fn gasm_abi_version() -> i32 {
957 $crate::ABI_VERSION
958 }
959 #[unsafe(no_mangle)]
960 pub extern "C" fn gasm_init() -> i32 {
961 $crate::thread::__set_main($main);
962 0
963 }
964 #[unsafe(no_mangle)]
965 pub extern "C" fn gasm_frame() {
966 gasm_loop_frame()
967 }
968 #[unsafe(no_mangle)]
969 #[inline(never)]
970 pub extern "C" fn gasm_loop_frame() {
971 $crate::thread::__threaded_loop_frame()
972 }
973 };
974 ($main:path, $exit:path) => {
975 $crate::threaded_main_loop!($main);
976 #[unsafe(no_mangle)]
977 pub extern "C" fn gasm_exit() {
978 $exit()
979 }
980 };
981}
982
983#[macro_export]
985macro_rules! main_loop {
986 ($main:path) => {
987 #[unsafe(no_mangle)]
988 pub extern "C" fn gasm_abi_version() -> i32 {
989 $crate::ABI_VERSION
990 }
991 #[unsafe(no_mangle)]
992 pub extern "C" fn gasm_init() -> i32 {
993 $crate::main_loop::set_main($main);
994 0
995 }
996 #[unsafe(no_mangle)]
997 pub extern "C" fn gasm_frame() {
998 gasm_loop_frame()
999 }
1000 #[unsafe(no_mangle)]
1001 #[inline(never)]
1002 pub extern "C" fn gasm_loop_frame() {
1003 $crate::main_loop::__loop_frame()
1004 }
1005 #[unsafe(no_mangle)]
1006 pub extern "C" fn gasm_run() -> i32 {
1007 $crate::main_loop::__run()
1008 }
1009 };
1010 ($main:path, $exit:path) => {
1011 $crate::main_loop!($main);
1012 #[unsafe(no_mangle)]
1013 pub extern "C" fn gasm_exit() {
1014 $exit()
1015 }
1016 };
1017}