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 max_threads() -> u32 {
51 if has("gasm.max_threads") { unsafe { sys::max_threads() } } else { 0 }
52}
53
54pub fn utc_offset_minutes() -> i32 {
55 if has("gasm.utc_offset_minutes") { unsafe { sys::utc_offset_minutes() } } else { 0 }
56}
57
58pub fn has(name: &str) -> bool {
62 unsafe { sys::has(name.as_ptr(), name.len() as u32) == 1 }
63}
64
65pub fn set_frame_rate(hz: f64) {
67 unsafe { sys::set_frame_rate(hz) }
68}
69
70pub fn video_set_aspect(num: u32, den: u32) -> bool {
75 static SUPPORTED: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
76 let ok = *SUPPORTED.get_or_init(|| has("gasm.video_set_aspect"));
77 if ok {
78 unsafe { sys::video_set_aspect(num, den) }
79 }
80 ok
81}
82
83#[macro_export]
87macro_rules! title {
88 ($t:literal) => {
89 #[cfg_attr(target_arch = "wasm32", unsafe(link_section = "gasm.title"))]
91 #[used]
92 static __GASM_TITLE: [u8; $t.len()] = $crate::__title_bytes($t);
93 };
94}
95
96#[doc(hidden)]
97pub const fn __title_bytes<const N: usize>(s: &str) -> [u8; N] {
98 let b = s.as_bytes();
99 let mut out = [0u8; N];
100 let mut i = 0;
101 while i < N {
102 out[i] = b[i];
103 i += 1;
104 }
105 out
106}
107
108pub fn set_title(title: &str) {
113 static SUPPORTED: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
114 if *SUPPORTED.get_or_init(|| has("gasm.set_title")) {
115 unsafe { sys::set_title(title.as_ptr(), title.len() as u32) }
116 }
117}
118
119pub fn param(name: &str) -> Option<String> {
121 let len = unsafe { sys::param(name.as_ptr(), name.len() as u32, std::ptr::null_mut(), 0) };
122 if len < 0 {
123 return None;
124 }
125 let mut buf = vec![0u8; len as usize];
126 unsafe { sys::param(name.as_ptr(), name.len() as u32, buf.as_mut_ptr(), len as u32) };
127 Some(String::from_utf8_lossy(&buf).into_owned())
128}
129
130pub fn exit(code: i32) -> ! {
132 unsafe { sys::proc_exit(code) }
133}
134
135pub fn present(rgba: &[u8], width: u32, height: u32, stride: u32) {
139 let need = (stride as u64)
140 .checked_mul(height.saturating_sub(1) as u64)
141 .and_then(|n| n.checked_add(width as u64 * 4))
142 .filter(|_| width > 0 && height > 0 && stride as u64 >= width as u64 * 4);
143 assert!(need.is_some_and(|n| rgba.len() as u64 >= n), "present: bad geometry or buffer too small");
144 unsafe { sys::video_present(rgba.as_ptr(), width, height, stride) }
145}
146
147pub mod audio {
148 use std::sync::atomic::{AtomicU32, Ordering};
149 static CHANNELS: AtomicU32 = AtomicU32::new(2);
150
151 pub fn config(sample_rate: u32, channels: u32) {
154 if (8000..=192_000).contains(&sample_rate) && (channels == 1 || channels == 2) {
155 CHANNELS.store(channels, Ordering::Relaxed);
156 }
157 unsafe { crate::sys::audio_config(sample_rate, channels) }
158 }
159
160 pub fn push(samples: &[f32]) {
163 let ch = CHANNELS.load(Ordering::Relaxed);
164 debug_assert!(samples.len() % ch as usize == 0, "audio::push: not a whole number of frames");
165 unsafe { crate::sys::audio_push(samples.as_ptr(), samples.len() as u32 / ch) }
166 }
167}
168
169#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
171pub struct Buttons(pub u32);
172
173impl Buttons {
174 pub const A: u32 = sys::GASM_BTN_A;
175 pub const B: u32 = sys::GASM_BTN_B;
176 pub const X: u32 = sys::GASM_BTN_X;
177 pub const Y: u32 = sys::GASM_BTN_Y;
178 pub const L: u32 = sys::GASM_BTN_L;
179 pub const R: u32 = sys::GASM_BTN_R;
180 pub const SELECT: u32 = sys::GASM_BTN_SELECT;
181 pub const START: u32 = sys::GASM_BTN_START;
182 pub const UP: u32 = sys::GASM_BTN_UP;
183 pub const DOWN: u32 = sys::GASM_BTN_DOWN;
184 pub const LEFT: u32 = sys::GASM_BTN_LEFT;
185 pub const RIGHT: u32 = sys::GASM_BTN_RIGHT;
186
187 pub fn held(self, mask: u32) -> bool {
188 self.0 & mask != 0
189 }
190}
191
192pub fn pad(player: u32) -> Buttons {
194 Buttons(unsafe { sys::input_pad(player) })
195}
196
197pub fn text_input() -> Option<String> {
200 let len = unsafe { sys::text_input(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::text_input(buf.as_mut_ptr(), len as u32) };
207 }
208 Some(String::from_utf8_lossy(&buf).into_owned())
209}
210
211pub fn asset_names() -> Vec<String> {
213 let n = unsafe { sys::asset_count() };
214 (0..n)
215 .filter_map(|i| {
216 let len = unsafe { sys::asset_name(i, std::ptr::null_mut(), 0) };
217 if len < 0 {
218 return None;
219 }
220 let mut buf = vec![0u8; len as usize];
221 if len > 0 {
222 unsafe { sys::asset_name(i, buf.as_mut_ptr(), len as u32) };
223 }
224 Some(String::from_utf8_lossy(&buf).into_owned())
225 })
226 .collect()
227}
228
229pub fn asset_size(name: &str) -> Option<u64> {
231 let n = unsafe { sys::asset_size64(name.as_ptr(), name.len() as u32) };
232 (n >= 0).then_some(n as u64)
233}
234
235pub fn asset_version(name: &str) -> Option<u32> {
239 if !has("gasm.asset_version") {
240 return asset_size(name).map(|_| 0);
241 }
242 let v = unsafe { sys::asset_version(name.as_ptr(), name.len() as u32) };
243 (v >= 0).then_some(v as u32)
244}
245
246pub fn asset(name: &str) -> Option<Vec<u8>> {
248 let n = unsafe { sys::asset_size(name.as_ptr(), name.len() as u32) };
249 if n < 0 {
250 return None;
251 }
252 let mut buf = vec![0u8; n as usize];
253 let got = unsafe { sys::asset_read(name.as_ptr(), name.len() as u32, buf.as_mut_ptr(), n as u32) };
254 buf.truncate(got.max(0) as usize);
255 Some(buf)
256}
257
258pub fn asset_read_at(name: &str, offset: u64, buf: &mut [u8]) -> Option<usize> {
261 let len = buf.len().min(u32::MAX as usize) as u32;
262 let n = unsafe { sys::asset_read_at64(name.as_ptr(), name.len() as u32, offset, buf.as_mut_ptr(), len) };
263 (n >= 0).then_some(n as usize)
264}
265
266pub mod storage {
273 use crate::sys;
274
275 pub fn get(key: &str) -> Option<Vec<u8>> {
276 let n = unsafe { sys::storage_get(key.as_ptr(), key.len() as u32, std::ptr::null_mut(), 0) };
277 if n < 0 {
278 return None;
279 }
280 let mut buf = vec![0u8; n as usize];
281 unsafe { sys::storage_get(key.as_ptr(), key.len() as u32, buf.as_mut_ptr(), n as u32) };
282 Some(buf)
283 }
284
285 #[derive(Clone, Copy, Debug, PartialEq, Eq)]
287 pub enum Error {
288 Key,
290 Size,
292 Quota,
294 Io,
296 }
297
298 pub fn try_set(key: &str, value: &[u8]) -> Result<(), Error> {
300 match unsafe { sys::storage_set(key.as_ptr(), key.len() as u32, value.as_ptr(), value.len() as u32) } {
301 sys::GASM_STORAGE_OK => Ok(()),
302 sys::GASM_STORAGE_ERR_KEY => Err(Error::Key),
303 sys::GASM_STORAGE_ERR_SIZE => Err(Error::Size),
304 sys::GASM_STORAGE_ERR_QUOTA => Err(Error::Quota),
305 _ => Err(Error::Io),
306 }
307 }
308
309 pub fn set(key: &str, value: &[u8]) -> bool {
311 try_set(key, value).is_ok()
312 }
313
314 pub fn delete(key: &str) -> bool {
316 unsafe { sys::storage_delete(key.as_ptr(), key.len() as u32) == 0 }
317 }
318
319 pub fn keys() -> Vec<String> {
321 let n = unsafe { sys::storage_count() };
322 (0..n)
323 .filter_map(|i| {
324 let len = unsafe { sys::storage_key(i, std::ptr::null_mut(), 0) };
325 if len < 0 {
326 return None;
327 }
328 let mut buf = vec![0u8; len as usize];
329 unsafe { sys::storage_key(i, buf.as_mut_ptr(), len as u32) };
330 Some(String::from_utf8_lossy(&buf).into_owned())
331 })
332 .collect()
333 }
334}
335
336pub mod clipboard {
340 use crate::sys;
341
342 pub fn available() -> bool {
344 crate::has("gasm:clipboard")
345 }
346
347 pub fn set_text(text: &str) -> bool {
349 unsafe { sys::clipboard_set_text(text.as_ptr(), text.len() as u32) == 0 }
350 }
351
352 pub fn pasted() -> Option<String> {
354 let n = unsafe { sys::clipboard_get_text(std::ptr::null_mut(), 0) };
355 if n < 0 {
356 return None;
357 }
358 let mut buf = vec![0u8; n as usize];
359 unsafe { sys::clipboard_get_text(buf.as_mut_ptr(), n as u32) };
360 Some(String::from_utf8_lossy(&buf).into_owned())
361 }
362}
363
364pub mod files {
368 use crate::sys;
369
370 pub fn available() -> bool {
372 crate::has("gasm:files")
373 }
374
375 #[derive(Clone, Copy, Debug, PartialEq, Eq)]
377 pub struct Save(i32);
378
379 #[derive(Clone, Copy, Debug, PartialEq, Eq)]
380 pub enum State {
381 Pending,
382 Saved,
383 Failed,
385 }
386
387 impl Save {
388 pub fn state(self) -> State {
389 match unsafe { sys::files_state(self.0) } {
390 sys::GASM_FILES_PENDING => State::Pending,
391 sys::GASM_FILES_SAVED => State::Saved,
392 _ => State::Failed,
393 }
394 }
395 }
396
397 pub fn save(name: &str, mime: &str, data: &[u8]) -> Option<Save> {
400 let h = unsafe { sys::files_save(name.as_ptr(), name.len() as u32, mime.as_ptr(), mime.len() as u32, data.as_ptr(), data.len() as u32) };
401 (h > 0).then(|| Save(h))
402 }
403}
404
405pub mod input {
417 use crate::sys;
418
419 pub const KEYS_RAW: u32 = sys::GASM_INPUT_KEYS_RAW;
421 pub const POINTER_HIDDEN: u32 = sys::GASM_INPUT_POINTER_HIDDEN;
423 pub const POINTER_LOCKED: u32 = sys::GASM_INPUT_POINTER_LOCKED;
425
426 pub const MOUSE_LEFT: u32 = sys::GASM_MOUSE_LEFT;
427 pub const MOUSE_RIGHT: u32 = sys::GASM_MOUSE_RIGHT;
428 pub const MOUSE_MIDDLE: u32 = sys::GASM_MOUSE_MIDDLE;
429 pub const MOUSE_BACK: u32 = sys::GASM_MOUSE_BACK;
430 pub const MOUSE_FORWARD: u32 = sys::GASM_MOUSE_FORWARD;
431
432 const KEY_BYTES: usize = sys::GASM_KEY_STATE_BYTES as usize;
433 const POINTER_BYTES: usize = sys::GASM_POINTER_BYTES as usize;
434 const GAMEPAD_BYTES: usize = sys::GASM_GAMEPAD_BYTES as usize;
435
436 pub fn set_mode(flags: u32) {
438 unsafe { sys::input_mode(flags) }
439 }
440
441 #[derive(Clone, Copy, Default, PartialEq, Eq)]
443 pub struct Keys(pub [u8; KEY_BYTES]);
444
445 impl Keys {
446 pub fn held(&self, code: u32) -> bool {
447 (code as usize) < KEY_BYTES * 8 && self.0[code as usize / 8] & (1 << (code % 8)) != 0
448 }
449 }
450
451 pub fn keys() -> Option<Keys> {
453 let mut k = Keys::default();
454 (unsafe { sys::key_state(k.0.as_mut_ptr(), KEY_BYTES as u32) } >= 0).then_some(k)
455 }
456
457 #[derive(Clone, Copy, Debug, PartialEq, Eq)]
458 pub struct KeyEvent {
459 pub code: u32,
460 pub down: bool,
461 }
462
463 pub fn key_events() -> Option<Vec<KeyEvent>> {
465 let n = unsafe { sys::key_events(std::ptr::null_mut(), 0) };
466 if n < 0 {
467 return None;
468 }
469 let mut b = vec![0u8; n as usize];
470 if n > 0 {
471 unsafe { sys::key_events(b.as_mut_ptr(), n as u32) };
472 }
473 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())
474 }
475
476 #[derive(Clone, Copy, Debug, Default, PartialEq)]
477 pub struct Pointer {
478 pub x: f32,
480 pub y: f32,
481 pub frame_x: f32,
483 pub frame_y: f32,
484 pub dx: f32,
486 pub dy: f32,
487 pub wheel_x: f32,
489 pub wheel_y: f32,
490 pub buttons: u32,
491 pub pressed: u32,
493 pub released: u32,
494 pub flags: u32,
495 }
496
497 impl Pointer {
498 pub fn inside(&self) -> bool {
499 self.flags & 1 != 0
500 }
501 pub fn locked(&self) -> bool {
502 self.flags & 4 != 0
503 }
504 }
505
506 pub fn pointer() -> Option<Pointer> {
508 let mut b = [0u8; POINTER_BYTES];
509 if unsafe { sys::pointer(b.as_mut_ptr(), POINTER_BYTES as u32) } < 0 {
510 return None;
511 }
512 let f = |o: u32| { let i = o as usize; f32::from_le_bytes([b[i], b[i + 1], b[i + 2], b[i + 3]]) };
513 let u = |o: u32| { let i = o as usize; u32::from_le_bytes([b[i], b[i + 1], b[i + 2], b[i + 3]]) };
514 Some(Pointer {
515 x: f(sys::GASM_POINTER_OFF_X), y: f(sys::GASM_POINTER_OFF_Y),
516 frame_x: f(sys::GASM_POINTER_OFF_FX), frame_y: f(sys::GASM_POINTER_OFF_FY),
517 dx: f(sys::GASM_POINTER_OFF_DX), dy: f(sys::GASM_POINTER_OFF_DY),
518 wheel_x: f(sys::GASM_POINTER_OFF_WHEEL_X), wheel_y: f(sys::GASM_POINTER_OFF_WHEEL_Y),
519 buttons: u(sys::GASM_POINTER_OFF_BUTTONS), pressed: u(sys::GASM_POINTER_OFF_PRESSED),
520 released: u(sys::GASM_POINTER_OFF_RELEASED), flags: u(sys::GASM_POINTER_OFF_FLAGS),
521 })
522 }
523
524 #[derive(Clone, Debug, Default, PartialEq)]
528 pub struct Gamepad {
529 pub connected: bool,
530 pub standard: bool,
531 pub buttons: Vec<f32>,
532 pub axes: Vec<f32>,
533 }
534
535 pub fn gamepad(slot: u32) -> Option<Gamepad> {
537 let mut b = [0u8; GAMEPAD_BYTES];
538 if unsafe { sys::gamepad(slot, b.as_mut_ptr(), GAMEPAD_BYTES as u32) } < 0 {
539 return None;
540 }
541 let u = |i: usize| u32::from_le_bytes([b[i], b[i + 1], b[i + 2], b[i + 3]]);
542 let f = |i: usize| f32::from_le_bytes([b[i], b[i + 1], b[i + 2], b[i + 3]]);
543 let (flags, nb, na) = (
544 u(sys::GASM_GAMEPAD_OFF_FLAGS as usize),
545 (u(sys::GASM_GAMEPAD_OFF_BUTTON_COUNT as usize) as usize).min(sys::GASM_GAMEPAD_BUTTONS as usize),
546 (u(sys::GASM_GAMEPAD_OFF_AXIS_COUNT as usize) as usize).min(sys::GASM_GAMEPAD_AXES as usize),
547 );
548 Some(Gamepad {
549 connected: flags & sys::GASM_GAMEPAD_CONNECTED != 0,
550 standard: flags & sys::GASM_GAMEPAD_STANDARD != 0,
551 buttons: (0..nb).map(|i| f(sys::GASM_GAMEPAD_OFF_BUTTONS as usize + i * 4)).collect(),
552 axes: (0..na).map(|i| f(sys::GASM_GAMEPAD_OFF_AXES as usize + i * 4)).collect(),
553 })
554 }
555
556 pub fn gamepad_name(slot: u32) -> Option<String> {
558 let n = unsafe { sys::gamepad_name(slot, std::ptr::null_mut(), 0) };
559 if n < 0 {
560 return None;
561 }
562 let mut b = vec![0u8; n as usize];
563 if n > 0 {
564 unsafe { sys::gamepad_name(slot, b.as_mut_ptr(), n as u32) };
565 }
566 Some(String::from_utf8_lossy(&b).into_owned())
567 }
568}
569
570pub use sys::keys;
572
573pub mod gfx {
577 use crate::sys;
578
579 #[derive(Clone, Copy, Debug, PartialEq, Eq)]
580 pub struct Shader(pub u32);
581 #[derive(Clone, Copy, Debug, PartialEq, Eq)]
582 pub struct Buffer(pub u32);
583 #[derive(Clone, Copy, Debug, PartialEq, Eq)]
584 pub struct Pipeline(pub u32);
585 #[derive(Clone, Copy, Debug, PartialEq, Eq)]
586 pub struct BindGroup(pub u32);
587 #[derive(Clone, Copy, Debug, PartialEq, Eq)]
588 pub struct BindGroupLayout(pub u32);
589 #[derive(Clone, Copy, Debug, PartialEq, Eq)]
590 pub struct Texture(pub u32);
591 #[derive(Clone, Copy, Debug, PartialEq, Eq)]
592 pub struct Sampler(pub u32);
593
594 pub const COPY_DST: u32 = sys::GASM_BUF_COPY_DST;
595 pub const INDEX: u32 = sys::GASM_BUF_INDEX;
596 pub const VERTEX: u32 = sys::GASM_BUF_VERTEX;
597 pub const UNIFORM: u32 = sys::GASM_BUF_UNIFORM;
598 pub const STORAGE: u32 = sys::GASM_BUF_STORAGE;
599
600 pub const STAGE_VERTEX: u32 = sys::GASM_STAGE_VERTEX;
602 pub const STAGE_FRAGMENT: u32 = sys::GASM_STAGE_FRAGMENT;
603 pub const OFFSET_ALIGNMENT: u32 = 256;
605
606 #[derive(Clone, Copy)]
607 pub enum IndexFormat {
608 U16 = sys::GASM_INDEX_U16 as isize,
609 U32 = sys::GASM_INDEX_U32 as isize,
610 }
611
612 pub trait Handle {
614 fn raw(&self) -> u32;
615 }
616 macro_rules! handles {
617 ($($t:ident),*) => { $(impl Handle for $t { fn raw(&self) -> u32 { self.0 } })* };
618 }
619 handles!(Shader, Buffer, Pipeline, BindGroup, BindGroupLayout, Texture, Sampler);
620
621 pub unsafe trait Pod: Copy {}
626 unsafe impl Pod for u8 {}
627 unsafe impl Pod for u16 {}
628 unsafe impl Pod for u32 {}
629 unsafe impl Pod for f32 {}
630 unsafe impl<T: Pod, const N: usize> Pod for [T; N] {}
631
632 pub fn as_bytes<T: Pod>(data: &[T]) -> &[u8] {
633 unsafe { std::slice::from_raw_parts(data.as_ptr() as *const u8, std::mem::size_of_val(data)) }
634 }
635
636 pub fn size() -> (u32, u32) {
638 unsafe { (sys::gfx_width(), sys::gfx_height()) }
639 }
640 pub fn create_shader(wgsl: &str) -> Shader {
641 Shader(unsafe { sys::gfx_create_shader(wgsl.as_ptr(), wgsl.len() as u32) })
642 }
643 pub fn create_buffer(size: u32, usage: u32) -> Buffer {
644 Buffer(unsafe { sys::gfx_create_buffer(size, usage) })
645 }
646 pub fn create_pipeline(json: &str) -> Pipeline {
649 Pipeline(unsafe { sys::gfx_create_pipeline(json.as_ptr(), json.len() as u32) })
650 }
651 pub fn create_bind_group(json: &str) -> BindGroup {
654 BindGroup(unsafe { sys::gfx_create_bind_group(json.as_ptr(), json.len() as u32) })
655 }
656 pub fn create_bind_group_layout(json: &str) -> BindGroupLayout {
659 BindGroupLayout(unsafe { sys::gfx_create_bind_group_layout(json.as_ptr(), json.len() as u32) })
660 }
661 pub fn create_texture(json: &str) -> Texture {
663 Texture(unsafe { sys::gfx_create_texture(json.as_ptr(), json.len() as u32) })
664 }
665 pub fn write_texture(t: Texture, mip: u32, x: u32, y: u32, w: u32, h: u32, rgba: &[u8]) {
667 unsafe { sys::gfx_write_texture(t.0, mip, x, y, w, h, rgba.as_ptr(), rgba.len() as u32) }
668 }
669 pub fn create_sampler(json: &str) -> Sampler {
671 Sampler(unsafe { sys::gfx_create_sampler(json.as_ptr(), json.len() as u32) })
672 }
673 pub fn write_buffer<T: Pod>(buf: Buffer, offset: u32, data: &[T]) {
675 let b = as_bytes(data);
676 unsafe { sys::gfx_write_buffer(buf.0, offset, b.as_ptr(), b.len() as u32) }
677 }
678 pub fn begin_frame(clear: [f32; 4]) -> bool {
680 unsafe { sys::gfx_begin_frame(clear[0], clear[1], clear[2], clear[3]) != 0 }
681 }
682 pub fn set_pipeline(p: Pipeline) {
683 unsafe { sys::gfx_set_pipeline(p.0) }
684 }
685 pub fn set_bind_group(index: u32, bg: BindGroup) {
686 unsafe { sys::gfx_set_bind_group(index, bg.0) }
687 }
688 pub fn set_bind_group_offsets(index: u32, bg: BindGroup, offsets: &[u32]) {
690 unsafe { sys::gfx_set_bind_group_offsets(index, bg.0, offsets.as_ptr(), offsets.len() as u32) }
691 }
692 pub fn set_viewport(x: f32, y: f32, w: f32, h: f32, min_depth: f32, max_depth: f32) {
694 unsafe { sys::gfx_set_viewport(x, y, w, h, min_depth, max_depth) }
695 }
696 pub fn set_scissor_rect(x: u32, y: u32, w: u32, h: u32) {
698 unsafe { sys::gfx_set_scissor_rect(x, y, w, h) }
699 }
700 pub fn set_vertex_buffer(slot: u32, buf: Buffer, offset: u32) {
701 unsafe { sys::gfx_set_vertex_buffer(slot, buf.0, offset) }
702 }
703 pub fn set_index_buffer(buf: Buffer, format: IndexFormat, offset: u32) {
704 unsafe { sys::gfx_set_index_buffer(buf.0, format as u32, offset) }
705 }
706 pub fn draw(vertices: u32, instances: u32, first_vertex: u32, first_instance: u32) {
707 unsafe { sys::gfx_draw(vertices, instances, first_vertex, first_instance) }
708 }
709 pub fn draw_indexed(indices: u32, instances: u32, first_index: u32, base_vertex: i32, first_instance: u32) {
710 unsafe { sys::gfx_draw_indexed(indices, instances, first_index, base_vertex, first_instance) }
711 }
712 pub fn end_frame() {
713 unsafe { sys::gfx_end_frame() }
714 }
715 pub fn destroy(h: impl Handle) {
718 unsafe { sys::gfx_destroy(h.raw()) }
719 }
720}
721
722pub mod net {
726 use crate::sys;
727
728 #[derive(Clone, Copy, Debug, PartialEq, Eq)]
729 pub enum State {
730 Connecting,
731 Open,
732 Closed,
733 Error,
734 }
735
736 pub enum Recv {
737 Message(Vec<u8>),
738 Empty,
739 Closed,
740 }
741
742 #[derive(Debug)]
743 pub struct Conn(i32);
744
745 impl Conn {
746 pub fn open(url: &str) -> Option<Conn> {
748 let h = unsafe { sys::net_open(url.as_ptr(), url.len() as u32) };
749 (h > 0).then(|| Conn(h)) }
751 pub fn state(&self) -> State {
752 match unsafe { sys::net_state(self.0) } {
753 0 => State::Connecting,
754 1 => State::Open,
755 2 => State::Closed,
756 _ => State::Error,
757 }
758 }
759 pub fn send(&self, msg: &[u8]) -> bool {
761 !msg.is_empty() && unsafe { sys::net_send(self.0, msg.as_ptr(), msg.len() as u32) } == 0
762 }
763 pub fn recv(&self) -> Recv {
765 let mut buf = [0u8; 1024];
766 let n = unsafe { sys::net_recv(self.0, buf.as_mut_ptr(), buf.len() as u32) };
767 match n {
768 0 => Recv::Empty,
769 n if n < 0 => Recv::Closed,
770 n if n as usize <= buf.len() => Recv::Message(buf[..n as usize].to_vec()),
771 n => {
772 let mut big = vec![0u8; n as usize];
773 unsafe { sys::net_recv(self.0, big.as_mut_ptr(), n as u32) };
774 Recv::Message(big)
775 }
776 }
777 }
778 }
779
780 impl Drop for Conn {
781 fn drop(&mut self) {
782 unsafe { sys::net_close(self.0) }
783 }
784 }
785}
786
787pub mod fetch {
802 use crate::sys;
803
804 pub fn available() -> bool {
806 crate::has("gasm:fetch")
807 }
808
809 #[derive(Clone, Copy, Debug, PartialEq, Eq)]
810 pub enum State {
811 Pending,
813 Headers,
815 Done,
817 Failed,
819 }
820
821 #[derive(Clone, Debug)]
823 pub struct Request {
824 method: String,
825 url: String,
826 headers: Vec<(String, String)>,
827 body: Vec<u8>,
828 }
829
830 fn json_str(s: &str, out: &mut String) {
831 out.push('"');
832 for c in s.chars() {
833 match c {
834 '"' => out.push_str("\\\""),
835 '\\' => out.push_str("\\\\"),
836 c if (c as u32) < 0x20 => out.push_str(&format!("\\u{:04x}", c as u32)),
837 c => out.push(c),
838 }
839 }
840 out.push('"');
841 }
842
843 impl Request {
844 pub fn new(method: &str, url: &str) -> Request {
845 Request { method: method.to_owned(), url: url.to_owned(), headers: Vec::new(), body: Vec::new() }
846 }
847 pub fn get(url: &str) -> Request {
848 Request::new("GET", url)
849 }
850 pub fn post(url: &str, body: impl Into<Vec<u8>>) -> Request {
851 Request::new("POST", url).body(body)
852 }
853 pub fn header(mut self, name: &str, value: &str) -> Request {
854 self.headers.push((name.to_owned(), value.to_owned()));
855 self
856 }
857 pub fn body(mut self, body: impl Into<Vec<u8>>) -> Request {
858 self.body = body.into();
859 self
860 }
861 pub fn describe(&self) -> String {
863 let mut d = String::from("{\"method\":");
864 json_str(&self.method, &mut d);
865 d.push_str(",\"url\":");
866 json_str(&self.url, &mut d);
867 d.push_str(",\"headers\":{");
868 for (i, (k, v)) in self.headers.iter().enumerate() {
869 if i > 0 {
870 d.push(',');
871 }
872 json_str(k, &mut d);
873 d.push(':');
874 json_str(v, &mut d);
875 }
876 d.push_str("}}");
877 d
878 }
879 pub fn send(&self) -> Option<Response> {
881 if !available() {
882 return None;
883 }
884 let d = self.describe();
885 let h = unsafe { sys::fetch_request(d.as_ptr(), d.len() as u32, self.body.as_ptr(), self.body.len() as u32) };
886 (h > 0).then(|| Response(h))
887 }
888 }
889
890 #[derive(Debug)]
892 pub struct Response(i32);
893
894 impl Response {
895 pub fn state(&self) -> State {
896 match unsafe { sys::fetch_state(self.0) } {
897 0 => State::Pending,
898 1 => State::Headers,
899 2 => State::Done,
900 _ => State::Failed,
901 }
902 }
903 pub fn status(&self) -> i32 {
905 unsafe { sys::fetch_status(self.0) }
906 }
907 pub fn headers(&self) -> Option<Vec<(String, String)>> {
909 let n = unsafe { sys::fetch_headers(self.0, std::ptr::null_mut(), 0) };
910 if n < 0 {
911 return None;
912 }
913 let mut buf = vec![0u8; n as usize];
914 unsafe { sys::fetch_headers(self.0, buf.as_mut_ptr(), n as u32) };
915 let text = String::from_utf8_lossy(&buf);
916 Some(text.lines().filter_map(|l| l.split_once(": ").map(|(k, v)| (k.to_owned(), v.to_owned()))).collect())
917 }
918 pub fn header(&self, name: &str) -> Option<String> {
920 self.headers()?.into_iter().find(|(k, _)| k == name).map(|(_, v)| v)
921 }
922 pub fn read(&mut self, buf: &mut [u8]) -> Option<usize> {
925 let n = unsafe { sys::fetch_read(self.0, buf.as_mut_ptr(), buf.len() as u32) };
926 (n >= 0).then_some(n as usize)
927 }
928 pub fn read_to_end(&mut self) -> Vec<u8> {
930 let mut out = Vec::new();
931 let mut buf = vec![0u8; 64 * 1024];
932 while let Some(n @ 1..) = self.read(&mut buf) {
933 out.extend_from_slice(&buf[..n]);
934 }
935 out
936 }
937 }
938
939 impl Drop for Response {
940 fn drop(&mut self) {
941 unsafe { sys::fetch_close(self.0) }
942 }
943 }
944}
945
946pub trait Game: Sized + 'static {
950 fn init() -> Result<Self, String>;
951 fn frame(&mut self);
952 fn exit(&mut self) {}
955}
956
957#[doc(hidden)]
958pub struct GameCell<T>(pub UnsafeCell<Option<T>>);
959#[cfg(target_arch = "wasm32")]
963unsafe impl<T> Sync for GameCell<T> {}
964#[cfg(not(target_arch = "wasm32"))]
965unsafe impl<T: Send> Sync for GameCell<T> {}
966
967#[doc(hidden)]
970pub unsafe fn __init<G: Game>(cell: &GameCell<G>) -> i32 {
971 std::panic::set_hook(Box::new(|info| log(&format!("panic: {info}"))));
972 match G::init() {
973 Ok(g) => {
974 unsafe { *cell.0.get() = Some(g) };
975 0
976 }
977 Err(e) => {
978 log(&format!("init failed: {e}"));
979 1
980 }
981 }
982}
983
984#[doc(hidden)]
987pub unsafe fn __frame<G: Game>(cell: &GameCell<G>) {
988 if let Some(g) = unsafe { (*cell.0.get()).as_mut() } {
989 g.frame();
990 }
991}
992
993#[doc(hidden)]
996pub unsafe fn __exit<G: Game>(cell: &GameCell<G>) {
997 if let Some(g) = unsafe { (*cell.0.get()).as_mut() } {
998 g.exit();
999 }
1000}
1001
1002#[macro_export]
1004macro_rules! game {
1005 ($t:ty) => {
1006 static __GASM_GAME: $crate::GameCell<$t> = $crate::GameCell(::std::cell::UnsafeCell::new(None));
1007
1008 #[unsafe(no_mangle)]
1009 pub extern "C" fn gasm_abi_version() -> i32 {
1010 $crate::ABI_VERSION
1011 }
1012 #[unsafe(no_mangle)]
1013 pub extern "C" fn gasm_init() -> i32 {
1014 unsafe { $crate::__init::<$t>(&__GASM_GAME) }
1015 }
1016 #[unsafe(no_mangle)]
1017 pub extern "C" fn gasm_frame() {
1018 unsafe { $crate::__frame::<$t>(&__GASM_GAME) }
1019 }
1020 #[unsafe(no_mangle)]
1021 pub extern "C" fn gasm_exit() {
1022 unsafe { $crate::__exit::<$t>(&__GASM_GAME) }
1023 }
1024 };
1025}
1026
1027pub mod main_loop {
1055 #![cfg_attr(not(target_arch = "wasm32"), allow(dead_code))]
1057
1058 #[cfg(target_arch = "wasm32")]
1059 mod asyncify {
1060 #[link(wasm_import_module = "asyncify")]
1061 unsafe extern "C" {
1062 pub fn start_unwind(data: *mut u8);
1063 pub fn stop_unwind();
1064 pub fn start_rewind(data: *mut u8);
1065 pub fn stop_rewind();
1066 }
1067 }
1068
1069 const STACK: usize = 1024 * 1024;
1070
1071 #[repr(C)]
1072 struct Data {
1073 cur: *mut u8,
1074 end: *mut u8,
1075 }
1076
1077 static mut DATA: Data = Data { cur: std::ptr::null_mut(), end: std::ptr::null_mut() };
1079 static mut STACK_BUF: [u8; STACK] = [0; STACK];
1080 static mut REWINDING: bool = false;
1081 static mut UNWINDING: bool = false;
1082 static mut STARTED: bool = false;
1083 static mut FINISHED: bool = false;
1084 static mut EXIT_CODE: i32 = 0;
1085 static mut MAIN: Option<fn() -> i32> = None;
1086 static mut FRAMES: u32 = 0;
1087 static mut RUN_MODE: bool = false;
1089
1090 fn wait_impl() {
1091 #[cfg(target_arch = "wasm32")]
1092 unsafe {
1093 if RUN_MODE {
1094 crate::sys::yield_frame();
1095 FRAMES += 1;
1096 return;
1097 }
1098 if REWINDING {
1099 asyncify::stop_rewind();
1100 REWINDING = false;
1101 FRAMES += 1;
1102 return;
1103 }
1104 DATA.cur = (&raw mut STACK_BUF).cast::<u8>();
1105 DATA.end = DATA.cur.add(STACK);
1106 UNWINDING = true;
1107 asyncify::start_unwind((&raw mut DATA).cast::<u8>());
1108 }
1109 }
1110
1111 static mut WAIT: fn() = wait_impl;
1114
1115 pub fn wait_frame() {
1117 if crate::thread::available() {
1118 return crate::thread::wait_frame(); }
1120 let f = unsafe { std::ptr::read_volatile(&raw const WAIT) };
1121 f()
1122 }
1123
1124 pub fn frames() -> u32 {
1126 if crate::thread::available() {
1127 return crate::thread::frames();
1128 }
1129 unsafe { FRAMES }
1130 }
1131
1132 #[doc(hidden)]
1133 pub fn set_main(f: fn() -> i32) {
1134 unsafe { MAIN = Some(f) }
1135 }
1136
1137 #[inline(never)]
1140 fn run_main() {
1141 unsafe {
1142 let rc = MAIN.map_or(0, |f| f());
1143 if !UNWINDING {
1144 EXIT_CODE = rc;
1145 FINISHED = true;
1146 }
1147 }
1148 }
1149
1150 #[doc(hidden)]
1153 pub fn __run() -> i32 {
1154 unsafe {
1155 RUN_MODE = true;
1156 STARTED = true;
1157 MAIN.map_or(0, |f| f())
1158 }
1159 }
1160
1161 #[doc(hidden)]
1166 #[inline(always)]
1167 pub fn __loop_frame() {
1168 unsafe {
1169 if FINISHED {
1170 return;
1171 }
1172 #[cfg(target_arch = "wasm32")]
1173 {
1174 if !STARTED {
1175 STARTED = true;
1176 } else {
1177 REWINDING = true;
1178 asyncify::start_rewind((&raw mut DATA).cast::<u8>());
1179 }
1180 run_main();
1181 if UNWINDING {
1182 asyncify::stop_unwind();
1183 UNWINDING = false;
1184 return;
1185 }
1186 }
1187 #[cfg(not(target_arch = "wasm32"))]
1188 {
1189 STARTED = true;
1190 run_main();
1191 }
1192 if FINISHED {
1193 crate::exit(EXIT_CODE);
1194 }
1195 }
1196 }
1197}
1198
1199#[macro_export]
1204macro_rules! threaded_main_loop {
1205 ($main:path) => {
1206 #[unsafe(no_mangle)]
1207 pub extern "C" fn gasm_abi_version() -> i32 {
1208 $crate::ABI_VERSION
1209 }
1210 #[unsafe(no_mangle)]
1211 pub extern "C" fn gasm_init() -> i32 {
1212 $crate::thread::__set_main($main);
1213 0
1214 }
1215 #[unsafe(no_mangle)]
1216 pub extern "C" fn gasm_frame() {
1217 gasm_loop_frame()
1218 }
1219 #[unsafe(no_mangle)]
1220 #[inline(never)]
1221 pub extern "C" fn gasm_loop_frame() {
1222 $crate::thread::__threaded_loop_frame()
1223 }
1224 };
1225 ($main:path, $exit:path) => {
1226 $crate::threaded_main_loop!($main);
1227 #[unsafe(no_mangle)]
1228 pub extern "C" fn gasm_exit() {
1229 $exit()
1230 }
1231 };
1232}
1233
1234#[macro_export]
1236macro_rules! main_loop {
1237 ($main:path) => {
1238 #[unsafe(no_mangle)]
1239 pub extern "C" fn gasm_abi_version() -> i32 {
1240 $crate::ABI_VERSION
1241 }
1242 #[unsafe(no_mangle)]
1243 pub extern "C" fn gasm_init() -> i32 {
1244 $crate::main_loop::set_main($main);
1245 0
1246 }
1247 #[unsafe(no_mangle)]
1248 pub extern "C" fn gasm_frame() {
1249 gasm_loop_frame()
1250 }
1251 #[unsafe(no_mangle)]
1252 #[inline(never)]
1253 pub extern "C" fn gasm_loop_frame() {
1254 $crate::main_loop::__loop_frame()
1255 }
1256 #[unsafe(no_mangle)]
1257 pub extern "C" fn gasm_run() -> i32 {
1258 $crate::main_loop::__run()
1259 }
1260 };
1261 ($main:path, $exit:path) => {
1262 $crate::main_loop!($main);
1263 #[unsafe(no_mangle)]
1264 pub extern "C" fn gasm_exit() {
1265 $exit()
1266 }
1267 };
1268}
1269
1270#[macro_export]
1278macro_rules! manifest {
1279 ($json:expr) => {
1280 #[cfg(target_arch = "wasm32")]
1281 #[unsafe(link_section = "gasm.manifest")]
1282 #[used]
1283 static __GASM_MANIFEST: [u8; $json.len()] = {
1284 let b = $json.as_bytes();
1285 let mut a = [0u8; $json.len()];
1286 let mut i = 0;
1287 while i < a.len() {
1288 a[i] = b[i];
1289 i += 1;
1290 }
1291 a
1292 };
1293 };
1294}