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 utc_offset_minutes() -> i32 {
49 if has("gasm.utc_offset_minutes") { unsafe { sys::utc_offset_minutes() } } else { 0 }
50}
51
52pub fn has(name: &str) -> bool {
56 unsafe { sys::has(name.as_ptr(), name.len() as u32) == 1 }
57}
58
59pub fn set_frame_rate(hz: f64) {
61 unsafe { sys::set_frame_rate(hz) }
62}
63
64pub fn video_set_aspect(num: u32, den: u32) -> bool {
69 static SUPPORTED: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
70 let ok = *SUPPORTED.get_or_init(|| has("gasm.video_set_aspect"));
71 if ok {
72 unsafe { sys::video_set_aspect(num, den) }
73 }
74 ok
75}
76
77#[macro_export]
81macro_rules! title {
82 ($t:literal) => {
83 #[cfg_attr(target_arch = "wasm32", unsafe(link_section = "gasm.title"))]
85 #[used]
86 static __GASM_TITLE: [u8; $t.len()] = $crate::__title_bytes($t);
87 };
88}
89
90#[doc(hidden)]
91pub const fn __title_bytes<const N: usize>(s: &str) -> [u8; N] {
92 let b = s.as_bytes();
93 let mut out = [0u8; N];
94 let mut i = 0;
95 while i < N {
96 out[i] = b[i];
97 i += 1;
98 }
99 out
100}
101
102pub fn set_title(title: &str) {
107 static SUPPORTED: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
108 if *SUPPORTED.get_or_init(|| has("gasm.set_title")) {
109 unsafe { sys::set_title(title.as_ptr(), title.len() as u32) }
110 }
111}
112
113pub fn param(name: &str) -> Option<String> {
115 let len = unsafe { sys::param(name.as_ptr(), name.len() as u32, std::ptr::null_mut(), 0) };
116 if len < 0 {
117 return None;
118 }
119 let mut buf = vec![0u8; len as usize];
120 unsafe { sys::param(name.as_ptr(), name.len() as u32, buf.as_mut_ptr(), len as u32) };
121 Some(String::from_utf8_lossy(&buf).into_owned())
122}
123
124pub fn exit(code: i32) -> ! {
126 unsafe { sys::proc_exit(code) }
127}
128
129pub fn present(rgba: &[u8], width: u32, height: u32, stride: u32) {
133 let need = (stride as u64)
134 .checked_mul(height.saturating_sub(1) as u64)
135 .and_then(|n| n.checked_add(width as u64 * 4))
136 .filter(|_| width > 0 && height > 0 && stride as u64 >= width as u64 * 4);
137 assert!(need.is_some_and(|n| rgba.len() as u64 >= n), "present: bad geometry or buffer too small");
138 unsafe { sys::video_present(rgba.as_ptr(), width, height, stride) }
139}
140
141pub mod audio {
142 use std::sync::atomic::{AtomicU32, Ordering};
143 static CHANNELS: AtomicU32 = AtomicU32::new(2);
144
145 pub fn config(sample_rate: u32, channels: u32) {
148 if (8000..=192_000).contains(&sample_rate) && (channels == 1 || channels == 2) {
149 CHANNELS.store(channels, Ordering::Relaxed);
150 }
151 unsafe { crate::sys::audio_config(sample_rate, channels) }
152 }
153
154 pub fn push(samples: &[f32]) {
157 let ch = CHANNELS.load(Ordering::Relaxed);
158 debug_assert!(samples.len() % ch as usize == 0, "audio::push: not a whole number of frames");
159 unsafe { crate::sys::audio_push(samples.as_ptr(), samples.len() as u32 / ch) }
160 }
161}
162
163#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
165pub struct Buttons(pub u32);
166
167impl Buttons {
168 pub const A: u32 = sys::GASM_BTN_A;
169 pub const B: u32 = sys::GASM_BTN_B;
170 pub const X: u32 = sys::GASM_BTN_X;
171 pub const Y: u32 = sys::GASM_BTN_Y;
172 pub const L: u32 = sys::GASM_BTN_L;
173 pub const R: u32 = sys::GASM_BTN_R;
174 pub const SELECT: u32 = sys::GASM_BTN_SELECT;
175 pub const START: u32 = sys::GASM_BTN_START;
176 pub const UP: u32 = sys::GASM_BTN_UP;
177 pub const DOWN: u32 = sys::GASM_BTN_DOWN;
178 pub const LEFT: u32 = sys::GASM_BTN_LEFT;
179 pub const RIGHT: u32 = sys::GASM_BTN_RIGHT;
180
181 pub fn held(self, mask: u32) -> bool {
182 self.0 & mask != 0
183 }
184}
185
186pub fn pad(player: u32) -> Buttons {
188 Buttons(unsafe { sys::input_pad(player) })
189}
190
191pub fn text_input() -> Option<String> {
194 let len = unsafe { sys::text_input(std::ptr::null_mut(), 0) };
195 if len < 0 {
196 return None;
197 }
198 let mut buf = vec![0u8; len as usize];
199 if len > 0 {
200 unsafe { sys::text_input(buf.as_mut_ptr(), len as u32) };
201 }
202 Some(String::from_utf8_lossy(&buf).into_owned())
203}
204
205pub fn asset_names() -> Vec<String> {
207 let n = unsafe { sys::asset_count() };
208 (0..n)
209 .filter_map(|i| {
210 let len = unsafe { sys::asset_name(i, std::ptr::null_mut(), 0) };
211 if len < 0 {
212 return None;
213 }
214 let mut buf = vec![0u8; len as usize];
215 if len > 0 {
216 unsafe { sys::asset_name(i, buf.as_mut_ptr(), len as u32) };
217 }
218 Some(String::from_utf8_lossy(&buf).into_owned())
219 })
220 .collect()
221}
222
223pub fn asset_size(name: &str) -> Option<u64> {
225 let n = unsafe { sys::asset_size64(name.as_ptr(), name.len() as u32) };
226 (n >= 0).then_some(n as u64)
227}
228
229pub fn asset_version(name: &str) -> Option<u32> {
233 if !has("gasm.asset_version") {
234 return asset_size(name).map(|_| 0);
235 }
236 let v = unsafe { sys::asset_version(name.as_ptr(), name.len() as u32) };
237 (v >= 0).then_some(v as u32)
238}
239
240pub fn asset(name: &str) -> Option<Vec<u8>> {
242 let n = unsafe { sys::asset_size(name.as_ptr(), name.len() as u32) };
243 if n < 0 {
244 return None;
245 }
246 let mut buf = vec![0u8; n as usize];
247 let got = unsafe { sys::asset_read(name.as_ptr(), name.len() as u32, buf.as_mut_ptr(), n as u32) };
248 buf.truncate(got.max(0) as usize);
249 Some(buf)
250}
251
252pub fn asset_read_at(name: &str, offset: u64, buf: &mut [u8]) -> Option<usize> {
255 let len = buf.len().min(u32::MAX as usize) as u32;
256 let n = unsafe { sys::asset_read_at64(name.as_ptr(), name.len() as u32, offset, buf.as_mut_ptr(), len) };
257 (n >= 0).then_some(n as usize)
258}
259
260pub mod storage {
267 use crate::sys;
268
269 pub fn get(key: &str) -> Option<Vec<u8>> {
270 let n = unsafe { sys::storage_get(key.as_ptr(), key.len() as u32, std::ptr::null_mut(), 0) };
271 if n < 0 {
272 return None;
273 }
274 let mut buf = vec![0u8; n as usize];
275 unsafe { sys::storage_get(key.as_ptr(), key.len() as u32, buf.as_mut_ptr(), n as u32) };
276 Some(buf)
277 }
278
279 #[derive(Clone, Copy, Debug, PartialEq, Eq)]
281 pub enum Error {
282 Key,
284 Size,
286 Quota,
288 Io,
290 }
291
292 pub fn try_set(key: &str, value: &[u8]) -> Result<(), Error> {
294 match unsafe { sys::storage_set(key.as_ptr(), key.len() as u32, value.as_ptr(), value.len() as u32) } {
295 sys::GASM_STORAGE_OK => Ok(()),
296 sys::GASM_STORAGE_ERR_KEY => Err(Error::Key),
297 sys::GASM_STORAGE_ERR_SIZE => Err(Error::Size),
298 sys::GASM_STORAGE_ERR_QUOTA => Err(Error::Quota),
299 _ => Err(Error::Io),
300 }
301 }
302
303 pub fn set(key: &str, value: &[u8]) -> bool {
305 try_set(key, value).is_ok()
306 }
307
308 pub fn delete(key: &str) -> bool {
310 unsafe { sys::storage_delete(key.as_ptr(), key.len() as u32) == 0 }
311 }
312
313 pub fn keys() -> Vec<String> {
315 let n = unsafe { sys::storage_count() };
316 (0..n)
317 .filter_map(|i| {
318 let len = unsafe { sys::storage_key(i, std::ptr::null_mut(), 0) };
319 if len < 0 {
320 return None;
321 }
322 let mut buf = vec![0u8; len as usize];
323 unsafe { sys::storage_key(i, buf.as_mut_ptr(), len as u32) };
324 Some(String::from_utf8_lossy(&buf).into_owned())
325 })
326 .collect()
327 }
328}
329
330pub mod input {
342 use crate::sys;
343
344 pub const KEYS_RAW: u32 = sys::GASM_INPUT_KEYS_RAW;
346 pub const POINTER_HIDDEN: u32 = sys::GASM_INPUT_POINTER_HIDDEN;
348 pub const POINTER_LOCKED: u32 = sys::GASM_INPUT_POINTER_LOCKED;
350
351 pub const MOUSE_LEFT: u32 = sys::GASM_MOUSE_LEFT;
352 pub const MOUSE_RIGHT: u32 = sys::GASM_MOUSE_RIGHT;
353 pub const MOUSE_MIDDLE: u32 = sys::GASM_MOUSE_MIDDLE;
354 pub const MOUSE_BACK: u32 = sys::GASM_MOUSE_BACK;
355 pub const MOUSE_FORWARD: u32 = sys::GASM_MOUSE_FORWARD;
356
357 const KEY_BYTES: usize = sys::GASM_KEY_STATE_BYTES as usize;
358 const POINTER_BYTES: usize = sys::GASM_POINTER_BYTES as usize;
359 const GAMEPAD_BYTES: usize = sys::GASM_GAMEPAD_BYTES as usize;
360
361 pub fn set_mode(flags: u32) {
363 unsafe { sys::input_mode(flags) }
364 }
365
366 #[derive(Clone, Copy, Default, PartialEq, Eq)]
368 pub struct Keys(pub [u8; KEY_BYTES]);
369
370 impl Keys {
371 pub fn held(&self, code: u32) -> bool {
372 (code as usize) < KEY_BYTES * 8 && self.0[code as usize / 8] & (1 << (code % 8)) != 0
373 }
374 }
375
376 pub fn keys() -> Option<Keys> {
378 let mut k = Keys::default();
379 (unsafe { sys::key_state(k.0.as_mut_ptr(), KEY_BYTES as u32) } >= 0).then_some(k)
380 }
381
382 #[derive(Clone, Copy, Debug, PartialEq, Eq)]
383 pub struct KeyEvent {
384 pub code: u32,
385 pub down: bool,
386 }
387
388 pub fn key_events() -> Option<Vec<KeyEvent>> {
390 let n = unsafe { sys::key_events(std::ptr::null_mut(), 0) };
391 if n < 0 {
392 return None;
393 }
394 let mut b = vec![0u8; n as usize];
395 if n > 0 {
396 unsafe { sys::key_events(b.as_mut_ptr(), n as u32) };
397 }
398 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())
399 }
400
401 #[derive(Clone, Copy, Debug, Default, PartialEq)]
402 pub struct Pointer {
403 pub x: f32,
405 pub y: f32,
406 pub frame_x: f32,
408 pub frame_y: f32,
409 pub dx: f32,
411 pub dy: f32,
412 pub wheel_x: f32,
414 pub wheel_y: f32,
415 pub buttons: u32,
416 pub pressed: u32,
418 pub released: u32,
419 pub flags: u32,
420 }
421
422 impl Pointer {
423 pub fn inside(&self) -> bool {
424 self.flags & 1 != 0
425 }
426 pub fn locked(&self) -> bool {
427 self.flags & 4 != 0
428 }
429 }
430
431 pub fn pointer() -> Option<Pointer> {
433 let mut b = [0u8; POINTER_BYTES];
434 if unsafe { sys::pointer(b.as_mut_ptr(), POINTER_BYTES as u32) } < 0 {
435 return None;
436 }
437 let f = |o: u32| { let i = o as usize; f32::from_le_bytes([b[i], b[i + 1], b[i + 2], b[i + 3]]) };
438 let u = |o: u32| { let i = o as usize; u32::from_le_bytes([b[i], b[i + 1], b[i + 2], b[i + 3]]) };
439 Some(Pointer {
440 x: f(sys::GASM_POINTER_OFF_X), y: f(sys::GASM_POINTER_OFF_Y),
441 frame_x: f(sys::GASM_POINTER_OFF_FX), frame_y: f(sys::GASM_POINTER_OFF_FY),
442 dx: f(sys::GASM_POINTER_OFF_DX), dy: f(sys::GASM_POINTER_OFF_DY),
443 wheel_x: f(sys::GASM_POINTER_OFF_WHEEL_X), wheel_y: f(sys::GASM_POINTER_OFF_WHEEL_Y),
444 buttons: u(sys::GASM_POINTER_OFF_BUTTONS), pressed: u(sys::GASM_POINTER_OFF_PRESSED),
445 released: u(sys::GASM_POINTER_OFF_RELEASED), flags: u(sys::GASM_POINTER_OFF_FLAGS),
446 })
447 }
448
449 #[derive(Clone, Debug, Default, PartialEq)]
453 pub struct Gamepad {
454 pub connected: bool,
455 pub standard: bool,
456 pub buttons: Vec<f32>,
457 pub axes: Vec<f32>,
458 }
459
460 pub fn gamepad(slot: u32) -> Option<Gamepad> {
462 let mut b = [0u8; GAMEPAD_BYTES];
463 if unsafe { sys::gamepad(slot, b.as_mut_ptr(), GAMEPAD_BYTES as u32) } < 0 {
464 return None;
465 }
466 let u = |i: usize| u32::from_le_bytes([b[i], b[i + 1], b[i + 2], b[i + 3]]);
467 let f = |i: usize| f32::from_le_bytes([b[i], b[i + 1], b[i + 2], b[i + 3]]);
468 let (flags, nb, na) = (
469 u(sys::GASM_GAMEPAD_OFF_FLAGS as usize),
470 (u(sys::GASM_GAMEPAD_OFF_BUTTON_COUNT as usize) as usize).min(sys::GASM_GAMEPAD_BUTTONS as usize),
471 (u(sys::GASM_GAMEPAD_OFF_AXIS_COUNT as usize) as usize).min(sys::GASM_GAMEPAD_AXES as usize),
472 );
473 Some(Gamepad {
474 connected: flags & sys::GASM_GAMEPAD_CONNECTED != 0,
475 standard: flags & sys::GASM_GAMEPAD_STANDARD != 0,
476 buttons: (0..nb).map(|i| f(sys::GASM_GAMEPAD_OFF_BUTTONS as usize + i * 4)).collect(),
477 axes: (0..na).map(|i| f(sys::GASM_GAMEPAD_OFF_AXES as usize + i * 4)).collect(),
478 })
479 }
480
481 pub fn gamepad_name(slot: u32) -> Option<String> {
483 let n = unsafe { sys::gamepad_name(slot, std::ptr::null_mut(), 0) };
484 if n < 0 {
485 return None;
486 }
487 let mut b = vec![0u8; n as usize];
488 if n > 0 {
489 unsafe { sys::gamepad_name(slot, b.as_mut_ptr(), n as u32) };
490 }
491 Some(String::from_utf8_lossy(&b).into_owned())
492 }
493}
494
495pub use sys::keys;
497
498pub mod gfx {
502 use crate::sys;
503
504 #[derive(Clone, Copy, Debug, PartialEq, Eq)]
505 pub struct Shader(pub u32);
506 #[derive(Clone, Copy, Debug, PartialEq, Eq)]
507 pub struct Buffer(pub u32);
508 #[derive(Clone, Copy, Debug, PartialEq, Eq)]
509 pub struct Pipeline(pub u32);
510 #[derive(Clone, Copy, Debug, PartialEq, Eq)]
511 pub struct BindGroup(pub u32);
512 #[derive(Clone, Copy, Debug, PartialEq, Eq)]
513 pub struct BindGroupLayout(pub u32);
514 #[derive(Clone, Copy, Debug, PartialEq, Eq)]
515 pub struct Texture(pub u32);
516 #[derive(Clone, Copy, Debug, PartialEq, Eq)]
517 pub struct Sampler(pub u32);
518
519 pub const COPY_DST: u32 = sys::GASM_BUF_COPY_DST;
520 pub const INDEX: u32 = sys::GASM_BUF_INDEX;
521 pub const VERTEX: u32 = sys::GASM_BUF_VERTEX;
522 pub const UNIFORM: u32 = sys::GASM_BUF_UNIFORM;
523 pub const STORAGE: u32 = sys::GASM_BUF_STORAGE;
524
525 pub const STAGE_VERTEX: u32 = sys::GASM_STAGE_VERTEX;
527 pub const STAGE_FRAGMENT: u32 = sys::GASM_STAGE_FRAGMENT;
528 pub const OFFSET_ALIGNMENT: u32 = 256;
530
531 #[derive(Clone, Copy)]
532 pub enum IndexFormat {
533 U16 = sys::GASM_INDEX_U16 as isize,
534 U32 = sys::GASM_INDEX_U32 as isize,
535 }
536
537 pub trait Handle {
539 fn raw(&self) -> u32;
540 }
541 macro_rules! handles {
542 ($($t:ident),*) => { $(impl Handle for $t { fn raw(&self) -> u32 { self.0 } })* };
543 }
544 handles!(Shader, Buffer, Pipeline, BindGroup, BindGroupLayout, Texture, Sampler);
545
546 pub unsafe trait Pod: Copy {}
551 unsafe impl Pod for u8 {}
552 unsafe impl Pod for u16 {}
553 unsafe impl Pod for u32 {}
554 unsafe impl Pod for f32 {}
555 unsafe impl<T: Pod, const N: usize> Pod for [T; N] {}
556
557 pub fn as_bytes<T: Pod>(data: &[T]) -> &[u8] {
558 unsafe { std::slice::from_raw_parts(data.as_ptr() as *const u8, std::mem::size_of_val(data)) }
559 }
560
561 pub fn size() -> (u32, u32) {
563 unsafe { (sys::gfx_width(), sys::gfx_height()) }
564 }
565 pub fn create_shader(wgsl: &str) -> Shader {
566 Shader(unsafe { sys::gfx_create_shader(wgsl.as_ptr(), wgsl.len() as u32) })
567 }
568 pub fn create_buffer(size: u32, usage: u32) -> Buffer {
569 Buffer(unsafe { sys::gfx_create_buffer(size, usage) })
570 }
571 pub fn create_pipeline(json: &str) -> Pipeline {
574 Pipeline(unsafe { sys::gfx_create_pipeline(json.as_ptr(), json.len() as u32) })
575 }
576 pub fn create_bind_group(json: &str) -> BindGroup {
579 BindGroup(unsafe { sys::gfx_create_bind_group(json.as_ptr(), json.len() as u32) })
580 }
581 pub fn create_bind_group_layout(json: &str) -> BindGroupLayout {
584 BindGroupLayout(unsafe { sys::gfx_create_bind_group_layout(json.as_ptr(), json.len() as u32) })
585 }
586 pub fn create_texture(json: &str) -> Texture {
588 Texture(unsafe { sys::gfx_create_texture(json.as_ptr(), json.len() as u32) })
589 }
590 pub fn write_texture(t: Texture, mip: u32, x: u32, y: u32, w: u32, h: u32, rgba: &[u8]) {
592 unsafe { sys::gfx_write_texture(t.0, mip, x, y, w, h, rgba.as_ptr(), rgba.len() as u32) }
593 }
594 pub fn create_sampler(json: &str) -> Sampler {
596 Sampler(unsafe { sys::gfx_create_sampler(json.as_ptr(), json.len() as u32) })
597 }
598 pub fn write_buffer<T: Pod>(buf: Buffer, offset: u32, data: &[T]) {
600 let b = as_bytes(data);
601 unsafe { sys::gfx_write_buffer(buf.0, offset, b.as_ptr(), b.len() as u32) }
602 }
603 pub fn begin_frame(clear: [f32; 4]) -> bool {
605 unsafe { sys::gfx_begin_frame(clear[0], clear[1], clear[2], clear[3]) != 0 }
606 }
607 pub fn set_pipeline(p: Pipeline) {
608 unsafe { sys::gfx_set_pipeline(p.0) }
609 }
610 pub fn set_bind_group(index: u32, bg: BindGroup) {
611 unsafe { sys::gfx_set_bind_group(index, bg.0) }
612 }
613 pub fn set_bind_group_offsets(index: u32, bg: BindGroup, offsets: &[u32]) {
615 unsafe { sys::gfx_set_bind_group_offsets(index, bg.0, offsets.as_ptr(), offsets.len() as u32) }
616 }
617 pub fn set_viewport(x: f32, y: f32, w: f32, h: f32, min_depth: f32, max_depth: f32) {
619 unsafe { sys::gfx_set_viewport(x, y, w, h, min_depth, max_depth) }
620 }
621 pub fn set_scissor_rect(x: u32, y: u32, w: u32, h: u32) {
623 unsafe { sys::gfx_set_scissor_rect(x, y, w, h) }
624 }
625 pub fn set_vertex_buffer(slot: u32, buf: Buffer, offset: u32) {
626 unsafe { sys::gfx_set_vertex_buffer(slot, buf.0, offset) }
627 }
628 pub fn set_index_buffer(buf: Buffer, format: IndexFormat, offset: u32) {
629 unsafe { sys::gfx_set_index_buffer(buf.0, format as u32, offset) }
630 }
631 pub fn draw(vertices: u32, instances: u32, first_vertex: u32, first_instance: u32) {
632 unsafe { sys::gfx_draw(vertices, instances, first_vertex, first_instance) }
633 }
634 pub fn draw_indexed(indices: u32, instances: u32, first_index: u32, base_vertex: i32, first_instance: u32) {
635 unsafe { sys::gfx_draw_indexed(indices, instances, first_index, base_vertex, first_instance) }
636 }
637 pub fn end_frame() {
638 unsafe { sys::gfx_end_frame() }
639 }
640 pub fn destroy(h: impl Handle) {
643 unsafe { sys::gfx_destroy(h.raw()) }
644 }
645}
646
647pub mod net {
651 use crate::sys;
652
653 #[derive(Clone, Copy, Debug, PartialEq, Eq)]
654 pub enum State {
655 Connecting,
656 Open,
657 Closed,
658 Error,
659 }
660
661 pub enum Recv {
662 Message(Vec<u8>),
663 Empty,
664 Closed,
665 }
666
667 #[derive(Debug)]
668 pub struct Conn(i32);
669
670 impl Conn {
671 pub fn open(url: &str) -> Option<Conn> {
673 let h = unsafe { sys::net_open(url.as_ptr(), url.len() as u32) };
674 (h > 0).then(|| Conn(h)) }
676 pub fn state(&self) -> State {
677 match unsafe { sys::net_state(self.0) } {
678 0 => State::Connecting,
679 1 => State::Open,
680 2 => State::Closed,
681 _ => State::Error,
682 }
683 }
684 pub fn send(&self, msg: &[u8]) -> bool {
686 !msg.is_empty() && unsafe { sys::net_send(self.0, msg.as_ptr(), msg.len() as u32) } == 0
687 }
688 pub fn recv(&self) -> Recv {
690 let mut buf = [0u8; 1024];
691 let n = unsafe { sys::net_recv(self.0, buf.as_mut_ptr(), buf.len() as u32) };
692 match n {
693 0 => Recv::Empty,
694 n if n < 0 => Recv::Closed,
695 n if n as usize <= buf.len() => Recv::Message(buf[..n as usize].to_vec()),
696 n => {
697 let mut big = vec![0u8; n as usize];
698 unsafe { sys::net_recv(self.0, big.as_mut_ptr(), n as u32) };
699 Recv::Message(big)
700 }
701 }
702 }
703 }
704
705 impl Drop for Conn {
706 fn drop(&mut self) {
707 unsafe { sys::net_close(self.0) }
708 }
709 }
710}
711
712pub mod fetch {
727 use crate::sys;
728
729 pub fn available() -> bool {
731 crate::has("gasm:fetch")
732 }
733
734 #[derive(Clone, Copy, Debug, PartialEq, Eq)]
735 pub enum State {
736 Pending,
738 Headers,
740 Done,
742 Failed,
744 }
745
746 #[derive(Clone, Debug)]
748 pub struct Request {
749 method: String,
750 url: String,
751 headers: Vec<(String, String)>,
752 body: Vec<u8>,
753 }
754
755 fn json_str(s: &str, out: &mut String) {
756 out.push('"');
757 for c in s.chars() {
758 match c {
759 '"' => out.push_str("\\\""),
760 '\\' => out.push_str("\\\\"),
761 c if (c as u32) < 0x20 => out.push_str(&format!("\\u{:04x}", c as u32)),
762 c => out.push(c),
763 }
764 }
765 out.push('"');
766 }
767
768 impl Request {
769 pub fn new(method: &str, url: &str) -> Request {
770 Request { method: method.to_owned(), url: url.to_owned(), headers: Vec::new(), body: Vec::new() }
771 }
772 pub fn get(url: &str) -> Request {
773 Request::new("GET", url)
774 }
775 pub fn post(url: &str, body: impl Into<Vec<u8>>) -> Request {
776 Request::new("POST", url).body(body)
777 }
778 pub fn header(mut self, name: &str, value: &str) -> Request {
779 self.headers.push((name.to_owned(), value.to_owned()));
780 self
781 }
782 pub fn body(mut self, body: impl Into<Vec<u8>>) -> Request {
783 self.body = body.into();
784 self
785 }
786 pub fn describe(&self) -> String {
788 let mut d = String::from("{\"method\":");
789 json_str(&self.method, &mut d);
790 d.push_str(",\"url\":");
791 json_str(&self.url, &mut d);
792 d.push_str(",\"headers\":{");
793 for (i, (k, v)) in self.headers.iter().enumerate() {
794 if i > 0 {
795 d.push(',');
796 }
797 json_str(k, &mut d);
798 d.push(':');
799 json_str(v, &mut d);
800 }
801 d.push_str("}}");
802 d
803 }
804 pub fn send(&self) -> Option<Response> {
806 if !available() {
807 return None;
808 }
809 let d = self.describe();
810 let h = unsafe { sys::fetch_request(d.as_ptr(), d.len() as u32, self.body.as_ptr(), self.body.len() as u32) };
811 (h > 0).then(|| Response(h))
812 }
813 }
814
815 #[derive(Debug)]
817 pub struct Response(i32);
818
819 impl Response {
820 pub fn state(&self) -> State {
821 match unsafe { sys::fetch_state(self.0) } {
822 0 => State::Pending,
823 1 => State::Headers,
824 2 => State::Done,
825 _ => State::Failed,
826 }
827 }
828 pub fn status(&self) -> i32 {
830 unsafe { sys::fetch_status(self.0) }
831 }
832 pub fn headers(&self) -> Option<Vec<(String, String)>> {
834 let n = unsafe { sys::fetch_headers(self.0, std::ptr::null_mut(), 0) };
835 if n < 0 {
836 return None;
837 }
838 let mut buf = vec![0u8; n as usize];
839 unsafe { sys::fetch_headers(self.0, buf.as_mut_ptr(), n as u32) };
840 let text = String::from_utf8_lossy(&buf);
841 Some(text.lines().filter_map(|l| l.split_once(": ").map(|(k, v)| (k.to_owned(), v.to_owned()))).collect())
842 }
843 pub fn header(&self, name: &str) -> Option<String> {
845 self.headers()?.into_iter().find(|(k, _)| k == name).map(|(_, v)| v)
846 }
847 pub fn read(&mut self, buf: &mut [u8]) -> Option<usize> {
850 let n = unsafe { sys::fetch_read(self.0, buf.as_mut_ptr(), buf.len() as u32) };
851 (n >= 0).then_some(n as usize)
852 }
853 pub fn read_to_end(&mut self) -> Vec<u8> {
855 let mut out = Vec::new();
856 let mut buf = vec![0u8; 64 * 1024];
857 while let Some(n @ 1..) = self.read(&mut buf) {
858 out.extend_from_slice(&buf[..n]);
859 }
860 out
861 }
862 }
863
864 impl Drop for Response {
865 fn drop(&mut self) {
866 unsafe { sys::fetch_close(self.0) }
867 }
868 }
869}
870
871pub trait Game: Sized + 'static {
875 fn init() -> Result<Self, String>;
876 fn frame(&mut self);
877 fn exit(&mut self) {}
880}
881
882#[doc(hidden)]
883pub struct GameCell<T>(pub UnsafeCell<Option<T>>);
884#[cfg(target_arch = "wasm32")]
888unsafe impl<T> Sync for GameCell<T> {}
889#[cfg(not(target_arch = "wasm32"))]
890unsafe impl<T: Send> Sync for GameCell<T> {}
891
892#[doc(hidden)]
895pub unsafe fn __init<G: Game>(cell: &GameCell<G>) -> i32 {
896 std::panic::set_hook(Box::new(|info| log(&format!("panic: {info}"))));
897 match G::init() {
898 Ok(g) => {
899 unsafe { *cell.0.get() = Some(g) };
900 0
901 }
902 Err(e) => {
903 log(&format!("init failed: {e}"));
904 1
905 }
906 }
907}
908
909#[doc(hidden)]
912pub unsafe fn __frame<G: Game>(cell: &GameCell<G>) {
913 if let Some(g) = unsafe { (*cell.0.get()).as_mut() } {
914 g.frame();
915 }
916}
917
918#[doc(hidden)]
921pub unsafe fn __exit<G: Game>(cell: &GameCell<G>) {
922 if let Some(g) = unsafe { (*cell.0.get()).as_mut() } {
923 g.exit();
924 }
925}
926
927#[macro_export]
929macro_rules! game {
930 ($t:ty) => {
931 static __GASM_GAME: $crate::GameCell<$t> = $crate::GameCell(::std::cell::UnsafeCell::new(None));
932
933 #[unsafe(no_mangle)]
934 pub extern "C" fn gasm_abi_version() -> i32 {
935 $crate::ABI_VERSION
936 }
937 #[unsafe(no_mangle)]
938 pub extern "C" fn gasm_init() -> i32 {
939 unsafe { $crate::__init::<$t>(&__GASM_GAME) }
940 }
941 #[unsafe(no_mangle)]
942 pub extern "C" fn gasm_frame() {
943 unsafe { $crate::__frame::<$t>(&__GASM_GAME) }
944 }
945 #[unsafe(no_mangle)]
946 pub extern "C" fn gasm_exit() {
947 unsafe { $crate::__exit::<$t>(&__GASM_GAME) }
948 }
949 };
950}
951
952pub mod main_loop {
980 #![cfg_attr(not(target_arch = "wasm32"), allow(dead_code))]
982
983 #[cfg(target_arch = "wasm32")]
984 mod asyncify {
985 #[link(wasm_import_module = "asyncify")]
986 unsafe extern "C" {
987 pub fn start_unwind(data: *mut u8);
988 pub fn stop_unwind();
989 pub fn start_rewind(data: *mut u8);
990 pub fn stop_rewind();
991 }
992 }
993
994 const STACK: usize = 1024 * 1024;
995
996 #[repr(C)]
997 struct Data {
998 cur: *mut u8,
999 end: *mut u8,
1000 }
1001
1002 static mut DATA: Data = Data { cur: std::ptr::null_mut(), end: std::ptr::null_mut() };
1004 static mut STACK_BUF: [u8; STACK] = [0; STACK];
1005 static mut REWINDING: bool = false;
1006 static mut UNWINDING: bool = false;
1007 static mut STARTED: bool = false;
1008 static mut FINISHED: bool = false;
1009 static mut EXIT_CODE: i32 = 0;
1010 static mut MAIN: Option<fn() -> i32> = None;
1011 static mut FRAMES: u32 = 0;
1012 static mut RUN_MODE: bool = false;
1014
1015 fn wait_impl() {
1016 #[cfg(target_arch = "wasm32")]
1017 unsafe {
1018 if RUN_MODE {
1019 crate::sys::yield_frame();
1020 FRAMES += 1;
1021 return;
1022 }
1023 if REWINDING {
1024 asyncify::stop_rewind();
1025 REWINDING = false;
1026 FRAMES += 1;
1027 return;
1028 }
1029 DATA.cur = (&raw mut STACK_BUF).cast::<u8>();
1030 DATA.end = DATA.cur.add(STACK);
1031 UNWINDING = true;
1032 asyncify::start_unwind((&raw mut DATA).cast::<u8>());
1033 }
1034 }
1035
1036 static mut WAIT: fn() = wait_impl;
1039
1040 pub fn wait_frame() {
1042 if crate::thread::available() {
1043 return crate::thread::wait_frame(); }
1045 let f = unsafe { std::ptr::read_volatile(&raw const WAIT) };
1046 f()
1047 }
1048
1049 pub fn frames() -> u32 {
1051 if crate::thread::available() {
1052 return crate::thread::frames();
1053 }
1054 unsafe { FRAMES }
1055 }
1056
1057 #[doc(hidden)]
1058 pub fn set_main(f: fn() -> i32) {
1059 unsafe { MAIN = Some(f) }
1060 }
1061
1062 #[inline(never)]
1065 fn run_main() {
1066 unsafe {
1067 let rc = MAIN.map_or(0, |f| f());
1068 if !UNWINDING {
1069 EXIT_CODE = rc;
1070 FINISHED = true;
1071 }
1072 }
1073 }
1074
1075 #[doc(hidden)]
1078 pub fn __run() -> i32 {
1079 unsafe {
1080 RUN_MODE = true;
1081 STARTED = true;
1082 MAIN.map_or(0, |f| f())
1083 }
1084 }
1085
1086 #[doc(hidden)]
1091 #[inline(always)]
1092 pub fn __loop_frame() {
1093 unsafe {
1094 if FINISHED {
1095 return;
1096 }
1097 #[cfg(target_arch = "wasm32")]
1098 {
1099 if !STARTED {
1100 STARTED = true;
1101 } else {
1102 REWINDING = true;
1103 asyncify::start_rewind((&raw mut DATA).cast::<u8>());
1104 }
1105 run_main();
1106 if UNWINDING {
1107 asyncify::stop_unwind();
1108 UNWINDING = false;
1109 return;
1110 }
1111 }
1112 #[cfg(not(target_arch = "wasm32"))]
1113 {
1114 STARTED = true;
1115 run_main();
1116 }
1117 if FINISHED {
1118 crate::exit(EXIT_CODE);
1119 }
1120 }
1121 }
1122}
1123
1124#[macro_export]
1129macro_rules! threaded_main_loop {
1130 ($main:path) => {
1131 #[unsafe(no_mangle)]
1132 pub extern "C" fn gasm_abi_version() -> i32 {
1133 $crate::ABI_VERSION
1134 }
1135 #[unsafe(no_mangle)]
1136 pub extern "C" fn gasm_init() -> i32 {
1137 $crate::thread::__set_main($main);
1138 0
1139 }
1140 #[unsafe(no_mangle)]
1141 pub extern "C" fn gasm_frame() {
1142 gasm_loop_frame()
1143 }
1144 #[unsafe(no_mangle)]
1145 #[inline(never)]
1146 pub extern "C" fn gasm_loop_frame() {
1147 $crate::thread::__threaded_loop_frame()
1148 }
1149 };
1150 ($main:path, $exit:path) => {
1151 $crate::threaded_main_loop!($main);
1152 #[unsafe(no_mangle)]
1153 pub extern "C" fn gasm_exit() {
1154 $exit()
1155 }
1156 };
1157}
1158
1159#[macro_export]
1161macro_rules! main_loop {
1162 ($main:path) => {
1163 #[unsafe(no_mangle)]
1164 pub extern "C" fn gasm_abi_version() -> i32 {
1165 $crate::ABI_VERSION
1166 }
1167 #[unsafe(no_mangle)]
1168 pub extern "C" fn gasm_init() -> i32 {
1169 $crate::main_loop::set_main($main);
1170 0
1171 }
1172 #[unsafe(no_mangle)]
1173 pub extern "C" fn gasm_frame() {
1174 gasm_loop_frame()
1175 }
1176 #[unsafe(no_mangle)]
1177 #[inline(never)]
1178 pub extern "C" fn gasm_loop_frame() {
1179 $crate::main_loop::__loop_frame()
1180 }
1181 #[unsafe(no_mangle)]
1182 pub extern "C" fn gasm_run() -> i32 {
1183 $crate::main_loop::__run()
1184 }
1185 };
1186 ($main:path, $exit:path) => {
1187 $crate::main_loop!($main);
1188 #[unsafe(no_mangle)]
1189 pub extern "C" fn gasm_exit() {
1190 $exit()
1191 }
1192 };
1193}