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 clipboard {
334 use crate::sys;
335
336 pub fn available() -> bool {
338 crate::has("gasm:clipboard")
339 }
340
341 pub fn set_text(text: &str) -> bool {
343 unsafe { sys::clipboard_set_text(text.as_ptr(), text.len() as u32) == 0 }
344 }
345
346 pub fn pasted() -> Option<String> {
348 let n = unsafe { sys::clipboard_get_text(std::ptr::null_mut(), 0) };
349 if n < 0 {
350 return None;
351 }
352 let mut buf = vec![0u8; n as usize];
353 unsafe { sys::clipboard_get_text(buf.as_mut_ptr(), n as u32) };
354 Some(String::from_utf8_lossy(&buf).into_owned())
355 }
356}
357
358pub mod files {
362 use crate::sys;
363
364 pub fn available() -> bool {
366 crate::has("gasm:files")
367 }
368
369 #[derive(Clone, Copy, Debug, PartialEq, Eq)]
371 pub struct Save(i32);
372
373 #[derive(Clone, Copy, Debug, PartialEq, Eq)]
374 pub enum State {
375 Pending,
376 Saved,
377 Failed,
379 }
380
381 impl Save {
382 pub fn state(self) -> State {
383 match unsafe { sys::files_state(self.0) } {
384 sys::GASM_FILES_PENDING => State::Pending,
385 sys::GASM_FILES_SAVED => State::Saved,
386 _ => State::Failed,
387 }
388 }
389 }
390
391 pub fn save(name: &str, mime: &str, data: &[u8]) -> Option<Save> {
394 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) };
395 (h > 0).then(|| Save(h))
396 }
397}
398
399pub mod input {
411 use crate::sys;
412
413 pub const KEYS_RAW: u32 = sys::GASM_INPUT_KEYS_RAW;
415 pub const POINTER_HIDDEN: u32 = sys::GASM_INPUT_POINTER_HIDDEN;
417 pub const POINTER_LOCKED: u32 = sys::GASM_INPUT_POINTER_LOCKED;
419
420 pub const MOUSE_LEFT: u32 = sys::GASM_MOUSE_LEFT;
421 pub const MOUSE_RIGHT: u32 = sys::GASM_MOUSE_RIGHT;
422 pub const MOUSE_MIDDLE: u32 = sys::GASM_MOUSE_MIDDLE;
423 pub const MOUSE_BACK: u32 = sys::GASM_MOUSE_BACK;
424 pub const MOUSE_FORWARD: u32 = sys::GASM_MOUSE_FORWARD;
425
426 const KEY_BYTES: usize = sys::GASM_KEY_STATE_BYTES as usize;
427 const POINTER_BYTES: usize = sys::GASM_POINTER_BYTES as usize;
428 const GAMEPAD_BYTES: usize = sys::GASM_GAMEPAD_BYTES as usize;
429
430 pub fn set_mode(flags: u32) {
432 unsafe { sys::input_mode(flags) }
433 }
434
435 #[derive(Clone, Copy, Default, PartialEq, Eq)]
437 pub struct Keys(pub [u8; KEY_BYTES]);
438
439 impl Keys {
440 pub fn held(&self, code: u32) -> bool {
441 (code as usize) < KEY_BYTES * 8 && self.0[code as usize / 8] & (1 << (code % 8)) != 0
442 }
443 }
444
445 pub fn keys() -> Option<Keys> {
447 let mut k = Keys::default();
448 (unsafe { sys::key_state(k.0.as_mut_ptr(), KEY_BYTES as u32) } >= 0).then_some(k)
449 }
450
451 #[derive(Clone, Copy, Debug, PartialEq, Eq)]
452 pub struct KeyEvent {
453 pub code: u32,
454 pub down: bool,
455 }
456
457 pub fn key_events() -> Option<Vec<KeyEvent>> {
459 let n = unsafe { sys::key_events(std::ptr::null_mut(), 0) };
460 if n < 0 {
461 return None;
462 }
463 let mut b = vec![0u8; n as usize];
464 if n > 0 {
465 unsafe { sys::key_events(b.as_mut_ptr(), n as u32) };
466 }
467 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())
468 }
469
470 #[derive(Clone, Copy, Debug, Default, PartialEq)]
471 pub struct Pointer {
472 pub x: f32,
474 pub y: f32,
475 pub frame_x: f32,
477 pub frame_y: f32,
478 pub dx: f32,
480 pub dy: f32,
481 pub wheel_x: f32,
483 pub wheel_y: f32,
484 pub buttons: u32,
485 pub pressed: u32,
487 pub released: u32,
488 pub flags: u32,
489 }
490
491 impl Pointer {
492 pub fn inside(&self) -> bool {
493 self.flags & 1 != 0
494 }
495 pub fn locked(&self) -> bool {
496 self.flags & 4 != 0
497 }
498 }
499
500 pub fn pointer() -> Option<Pointer> {
502 let mut b = [0u8; POINTER_BYTES];
503 if unsafe { sys::pointer(b.as_mut_ptr(), POINTER_BYTES as u32) } < 0 {
504 return None;
505 }
506 let f = |o: u32| { let i = o as usize; f32::from_le_bytes([b[i], b[i + 1], b[i + 2], b[i + 3]]) };
507 let u = |o: u32| { let i = o as usize; u32::from_le_bytes([b[i], b[i + 1], b[i + 2], b[i + 3]]) };
508 Some(Pointer {
509 x: f(sys::GASM_POINTER_OFF_X), y: f(sys::GASM_POINTER_OFF_Y),
510 frame_x: f(sys::GASM_POINTER_OFF_FX), frame_y: f(sys::GASM_POINTER_OFF_FY),
511 dx: f(sys::GASM_POINTER_OFF_DX), dy: f(sys::GASM_POINTER_OFF_DY),
512 wheel_x: f(sys::GASM_POINTER_OFF_WHEEL_X), wheel_y: f(sys::GASM_POINTER_OFF_WHEEL_Y),
513 buttons: u(sys::GASM_POINTER_OFF_BUTTONS), pressed: u(sys::GASM_POINTER_OFF_PRESSED),
514 released: u(sys::GASM_POINTER_OFF_RELEASED), flags: u(sys::GASM_POINTER_OFF_FLAGS),
515 })
516 }
517
518 #[derive(Clone, Debug, Default, PartialEq)]
522 pub struct Gamepad {
523 pub connected: bool,
524 pub standard: bool,
525 pub buttons: Vec<f32>,
526 pub axes: Vec<f32>,
527 }
528
529 pub fn gamepad(slot: u32) -> Option<Gamepad> {
531 let mut b = [0u8; GAMEPAD_BYTES];
532 if unsafe { sys::gamepad(slot, b.as_mut_ptr(), GAMEPAD_BYTES as u32) } < 0 {
533 return None;
534 }
535 let u = |i: usize| u32::from_le_bytes([b[i], b[i + 1], b[i + 2], b[i + 3]]);
536 let f = |i: usize| f32::from_le_bytes([b[i], b[i + 1], b[i + 2], b[i + 3]]);
537 let (flags, nb, na) = (
538 u(sys::GASM_GAMEPAD_OFF_FLAGS as usize),
539 (u(sys::GASM_GAMEPAD_OFF_BUTTON_COUNT as usize) as usize).min(sys::GASM_GAMEPAD_BUTTONS as usize),
540 (u(sys::GASM_GAMEPAD_OFF_AXIS_COUNT as usize) as usize).min(sys::GASM_GAMEPAD_AXES as usize),
541 );
542 Some(Gamepad {
543 connected: flags & sys::GASM_GAMEPAD_CONNECTED != 0,
544 standard: flags & sys::GASM_GAMEPAD_STANDARD != 0,
545 buttons: (0..nb).map(|i| f(sys::GASM_GAMEPAD_OFF_BUTTONS as usize + i * 4)).collect(),
546 axes: (0..na).map(|i| f(sys::GASM_GAMEPAD_OFF_AXES as usize + i * 4)).collect(),
547 })
548 }
549
550 pub fn gamepad_name(slot: u32) -> Option<String> {
552 let n = unsafe { sys::gamepad_name(slot, std::ptr::null_mut(), 0) };
553 if n < 0 {
554 return None;
555 }
556 let mut b = vec![0u8; n as usize];
557 if n > 0 {
558 unsafe { sys::gamepad_name(slot, b.as_mut_ptr(), n as u32) };
559 }
560 Some(String::from_utf8_lossy(&b).into_owned())
561 }
562}
563
564pub use sys::keys;
566
567pub mod gfx {
571 use crate::sys;
572
573 #[derive(Clone, Copy, Debug, PartialEq, Eq)]
574 pub struct Shader(pub u32);
575 #[derive(Clone, Copy, Debug, PartialEq, Eq)]
576 pub struct Buffer(pub u32);
577 #[derive(Clone, Copy, Debug, PartialEq, Eq)]
578 pub struct Pipeline(pub u32);
579 #[derive(Clone, Copy, Debug, PartialEq, Eq)]
580 pub struct BindGroup(pub u32);
581 #[derive(Clone, Copy, Debug, PartialEq, Eq)]
582 pub struct BindGroupLayout(pub u32);
583 #[derive(Clone, Copy, Debug, PartialEq, Eq)]
584 pub struct Texture(pub u32);
585 #[derive(Clone, Copy, Debug, PartialEq, Eq)]
586 pub struct Sampler(pub u32);
587
588 pub const COPY_DST: u32 = sys::GASM_BUF_COPY_DST;
589 pub const INDEX: u32 = sys::GASM_BUF_INDEX;
590 pub const VERTEX: u32 = sys::GASM_BUF_VERTEX;
591 pub const UNIFORM: u32 = sys::GASM_BUF_UNIFORM;
592 pub const STORAGE: u32 = sys::GASM_BUF_STORAGE;
593
594 pub const STAGE_VERTEX: u32 = sys::GASM_STAGE_VERTEX;
596 pub const STAGE_FRAGMENT: u32 = sys::GASM_STAGE_FRAGMENT;
597 pub const OFFSET_ALIGNMENT: u32 = 256;
599
600 #[derive(Clone, Copy)]
601 pub enum IndexFormat {
602 U16 = sys::GASM_INDEX_U16 as isize,
603 U32 = sys::GASM_INDEX_U32 as isize,
604 }
605
606 pub trait Handle {
608 fn raw(&self) -> u32;
609 }
610 macro_rules! handles {
611 ($($t:ident),*) => { $(impl Handle for $t { fn raw(&self) -> u32 { self.0 } })* };
612 }
613 handles!(Shader, Buffer, Pipeline, BindGroup, BindGroupLayout, Texture, Sampler);
614
615 pub unsafe trait Pod: Copy {}
620 unsafe impl Pod for u8 {}
621 unsafe impl Pod for u16 {}
622 unsafe impl Pod for u32 {}
623 unsafe impl Pod for f32 {}
624 unsafe impl<T: Pod, const N: usize> Pod for [T; N] {}
625
626 pub fn as_bytes<T: Pod>(data: &[T]) -> &[u8] {
627 unsafe { std::slice::from_raw_parts(data.as_ptr() as *const u8, std::mem::size_of_val(data)) }
628 }
629
630 pub fn size() -> (u32, u32) {
632 unsafe { (sys::gfx_width(), sys::gfx_height()) }
633 }
634 pub fn create_shader(wgsl: &str) -> Shader {
635 Shader(unsafe { sys::gfx_create_shader(wgsl.as_ptr(), wgsl.len() as u32) })
636 }
637 pub fn create_buffer(size: u32, usage: u32) -> Buffer {
638 Buffer(unsafe { sys::gfx_create_buffer(size, usage) })
639 }
640 pub fn create_pipeline(json: &str) -> Pipeline {
643 Pipeline(unsafe { sys::gfx_create_pipeline(json.as_ptr(), json.len() as u32) })
644 }
645 pub fn create_bind_group(json: &str) -> BindGroup {
648 BindGroup(unsafe { sys::gfx_create_bind_group(json.as_ptr(), json.len() as u32) })
649 }
650 pub fn create_bind_group_layout(json: &str) -> BindGroupLayout {
653 BindGroupLayout(unsafe { sys::gfx_create_bind_group_layout(json.as_ptr(), json.len() as u32) })
654 }
655 pub fn create_texture(json: &str) -> Texture {
657 Texture(unsafe { sys::gfx_create_texture(json.as_ptr(), json.len() as u32) })
658 }
659 pub fn write_texture(t: Texture, mip: u32, x: u32, y: u32, w: u32, h: u32, rgba: &[u8]) {
661 unsafe { sys::gfx_write_texture(t.0, mip, x, y, w, h, rgba.as_ptr(), rgba.len() as u32) }
662 }
663 pub fn create_sampler(json: &str) -> Sampler {
665 Sampler(unsafe { sys::gfx_create_sampler(json.as_ptr(), json.len() as u32) })
666 }
667 pub fn write_buffer<T: Pod>(buf: Buffer, offset: u32, data: &[T]) {
669 let b = as_bytes(data);
670 unsafe { sys::gfx_write_buffer(buf.0, offset, b.as_ptr(), b.len() as u32) }
671 }
672 pub fn begin_frame(clear: [f32; 4]) -> bool {
674 unsafe { sys::gfx_begin_frame(clear[0], clear[1], clear[2], clear[3]) != 0 }
675 }
676 pub fn set_pipeline(p: Pipeline) {
677 unsafe { sys::gfx_set_pipeline(p.0) }
678 }
679 pub fn set_bind_group(index: u32, bg: BindGroup) {
680 unsafe { sys::gfx_set_bind_group(index, bg.0) }
681 }
682 pub fn set_bind_group_offsets(index: u32, bg: BindGroup, offsets: &[u32]) {
684 unsafe { sys::gfx_set_bind_group_offsets(index, bg.0, offsets.as_ptr(), offsets.len() as u32) }
685 }
686 pub fn set_viewport(x: f32, y: f32, w: f32, h: f32, min_depth: f32, max_depth: f32) {
688 unsafe { sys::gfx_set_viewport(x, y, w, h, min_depth, max_depth) }
689 }
690 pub fn set_scissor_rect(x: u32, y: u32, w: u32, h: u32) {
692 unsafe { sys::gfx_set_scissor_rect(x, y, w, h) }
693 }
694 pub fn set_vertex_buffer(slot: u32, buf: Buffer, offset: u32) {
695 unsafe { sys::gfx_set_vertex_buffer(slot, buf.0, offset) }
696 }
697 pub fn set_index_buffer(buf: Buffer, format: IndexFormat, offset: u32) {
698 unsafe { sys::gfx_set_index_buffer(buf.0, format as u32, offset) }
699 }
700 pub fn draw(vertices: u32, instances: u32, first_vertex: u32, first_instance: u32) {
701 unsafe { sys::gfx_draw(vertices, instances, first_vertex, first_instance) }
702 }
703 pub fn draw_indexed(indices: u32, instances: u32, first_index: u32, base_vertex: i32, first_instance: u32) {
704 unsafe { sys::gfx_draw_indexed(indices, instances, first_index, base_vertex, first_instance) }
705 }
706 pub fn end_frame() {
707 unsafe { sys::gfx_end_frame() }
708 }
709 pub fn destroy(h: impl Handle) {
712 unsafe { sys::gfx_destroy(h.raw()) }
713 }
714}
715
716pub mod net {
720 use crate::sys;
721
722 #[derive(Clone, Copy, Debug, PartialEq, Eq)]
723 pub enum State {
724 Connecting,
725 Open,
726 Closed,
727 Error,
728 }
729
730 pub enum Recv {
731 Message(Vec<u8>),
732 Empty,
733 Closed,
734 }
735
736 #[derive(Debug)]
737 pub struct Conn(i32);
738
739 impl Conn {
740 pub fn open(url: &str) -> Option<Conn> {
742 let h = unsafe { sys::net_open(url.as_ptr(), url.len() as u32) };
743 (h > 0).then(|| Conn(h)) }
745 pub fn state(&self) -> State {
746 match unsafe { sys::net_state(self.0) } {
747 0 => State::Connecting,
748 1 => State::Open,
749 2 => State::Closed,
750 _ => State::Error,
751 }
752 }
753 pub fn send(&self, msg: &[u8]) -> bool {
755 !msg.is_empty() && unsafe { sys::net_send(self.0, msg.as_ptr(), msg.len() as u32) } == 0
756 }
757 pub fn recv(&self) -> Recv {
759 let mut buf = [0u8; 1024];
760 let n = unsafe { sys::net_recv(self.0, buf.as_mut_ptr(), buf.len() as u32) };
761 match n {
762 0 => Recv::Empty,
763 n if n < 0 => Recv::Closed,
764 n if n as usize <= buf.len() => Recv::Message(buf[..n as usize].to_vec()),
765 n => {
766 let mut big = vec![0u8; n as usize];
767 unsafe { sys::net_recv(self.0, big.as_mut_ptr(), n as u32) };
768 Recv::Message(big)
769 }
770 }
771 }
772 }
773
774 impl Drop for Conn {
775 fn drop(&mut self) {
776 unsafe { sys::net_close(self.0) }
777 }
778 }
779}
780
781pub mod fetch {
796 use crate::sys;
797
798 pub fn available() -> bool {
800 crate::has("gasm:fetch")
801 }
802
803 #[derive(Clone, Copy, Debug, PartialEq, Eq)]
804 pub enum State {
805 Pending,
807 Headers,
809 Done,
811 Failed,
813 }
814
815 #[derive(Clone, Debug)]
817 pub struct Request {
818 method: String,
819 url: String,
820 headers: Vec<(String, String)>,
821 body: Vec<u8>,
822 }
823
824 fn json_str(s: &str, out: &mut String) {
825 out.push('"');
826 for c in s.chars() {
827 match c {
828 '"' => out.push_str("\\\""),
829 '\\' => out.push_str("\\\\"),
830 c if (c as u32) < 0x20 => out.push_str(&format!("\\u{:04x}", c as u32)),
831 c => out.push(c),
832 }
833 }
834 out.push('"');
835 }
836
837 impl Request {
838 pub fn new(method: &str, url: &str) -> Request {
839 Request { method: method.to_owned(), url: url.to_owned(), headers: Vec::new(), body: Vec::new() }
840 }
841 pub fn get(url: &str) -> Request {
842 Request::new("GET", url)
843 }
844 pub fn post(url: &str, body: impl Into<Vec<u8>>) -> Request {
845 Request::new("POST", url).body(body)
846 }
847 pub fn header(mut self, name: &str, value: &str) -> Request {
848 self.headers.push((name.to_owned(), value.to_owned()));
849 self
850 }
851 pub fn body(mut self, body: impl Into<Vec<u8>>) -> Request {
852 self.body = body.into();
853 self
854 }
855 pub fn describe(&self) -> String {
857 let mut d = String::from("{\"method\":");
858 json_str(&self.method, &mut d);
859 d.push_str(",\"url\":");
860 json_str(&self.url, &mut d);
861 d.push_str(",\"headers\":{");
862 for (i, (k, v)) in self.headers.iter().enumerate() {
863 if i > 0 {
864 d.push(',');
865 }
866 json_str(k, &mut d);
867 d.push(':');
868 json_str(v, &mut d);
869 }
870 d.push_str("}}");
871 d
872 }
873 pub fn send(&self) -> Option<Response> {
875 if !available() {
876 return None;
877 }
878 let d = self.describe();
879 let h = unsafe { sys::fetch_request(d.as_ptr(), d.len() as u32, self.body.as_ptr(), self.body.len() as u32) };
880 (h > 0).then(|| Response(h))
881 }
882 }
883
884 #[derive(Debug)]
886 pub struct Response(i32);
887
888 impl Response {
889 pub fn state(&self) -> State {
890 match unsafe { sys::fetch_state(self.0) } {
891 0 => State::Pending,
892 1 => State::Headers,
893 2 => State::Done,
894 _ => State::Failed,
895 }
896 }
897 pub fn status(&self) -> i32 {
899 unsafe { sys::fetch_status(self.0) }
900 }
901 pub fn headers(&self) -> Option<Vec<(String, String)>> {
903 let n = unsafe { sys::fetch_headers(self.0, std::ptr::null_mut(), 0) };
904 if n < 0 {
905 return None;
906 }
907 let mut buf = vec![0u8; n as usize];
908 unsafe { sys::fetch_headers(self.0, buf.as_mut_ptr(), n as u32) };
909 let text = String::from_utf8_lossy(&buf);
910 Some(text.lines().filter_map(|l| l.split_once(": ").map(|(k, v)| (k.to_owned(), v.to_owned()))).collect())
911 }
912 pub fn header(&self, name: &str) -> Option<String> {
914 self.headers()?.into_iter().find(|(k, _)| k == name).map(|(_, v)| v)
915 }
916 pub fn read(&mut self, buf: &mut [u8]) -> Option<usize> {
919 let n = unsafe { sys::fetch_read(self.0, buf.as_mut_ptr(), buf.len() as u32) };
920 (n >= 0).then_some(n as usize)
921 }
922 pub fn read_to_end(&mut self) -> Vec<u8> {
924 let mut out = Vec::new();
925 let mut buf = vec![0u8; 64 * 1024];
926 while let Some(n @ 1..) = self.read(&mut buf) {
927 out.extend_from_slice(&buf[..n]);
928 }
929 out
930 }
931 }
932
933 impl Drop for Response {
934 fn drop(&mut self) {
935 unsafe { sys::fetch_close(self.0) }
936 }
937 }
938}
939
940pub trait Game: Sized + 'static {
944 fn init() -> Result<Self, String>;
945 fn frame(&mut self);
946 fn exit(&mut self) {}
949}
950
951#[doc(hidden)]
952pub struct GameCell<T>(pub UnsafeCell<Option<T>>);
953#[cfg(target_arch = "wasm32")]
957unsafe impl<T> Sync for GameCell<T> {}
958#[cfg(not(target_arch = "wasm32"))]
959unsafe impl<T: Send> Sync for GameCell<T> {}
960
961#[doc(hidden)]
964pub unsafe fn __init<G: Game>(cell: &GameCell<G>) -> i32 {
965 std::panic::set_hook(Box::new(|info| log(&format!("panic: {info}"))));
966 match G::init() {
967 Ok(g) => {
968 unsafe { *cell.0.get() = Some(g) };
969 0
970 }
971 Err(e) => {
972 log(&format!("init failed: {e}"));
973 1
974 }
975 }
976}
977
978#[doc(hidden)]
981pub unsafe fn __frame<G: Game>(cell: &GameCell<G>) {
982 if let Some(g) = unsafe { (*cell.0.get()).as_mut() } {
983 g.frame();
984 }
985}
986
987#[doc(hidden)]
990pub unsafe fn __exit<G: Game>(cell: &GameCell<G>) {
991 if let Some(g) = unsafe { (*cell.0.get()).as_mut() } {
992 g.exit();
993 }
994}
995
996#[macro_export]
998macro_rules! game {
999 ($t:ty) => {
1000 static __GASM_GAME: $crate::GameCell<$t> = $crate::GameCell(::std::cell::UnsafeCell::new(None));
1001
1002 #[unsafe(no_mangle)]
1003 pub extern "C" fn gasm_abi_version() -> i32 {
1004 $crate::ABI_VERSION
1005 }
1006 #[unsafe(no_mangle)]
1007 pub extern "C" fn gasm_init() -> i32 {
1008 unsafe { $crate::__init::<$t>(&__GASM_GAME) }
1009 }
1010 #[unsafe(no_mangle)]
1011 pub extern "C" fn gasm_frame() {
1012 unsafe { $crate::__frame::<$t>(&__GASM_GAME) }
1013 }
1014 #[unsafe(no_mangle)]
1015 pub extern "C" fn gasm_exit() {
1016 unsafe { $crate::__exit::<$t>(&__GASM_GAME) }
1017 }
1018 };
1019}
1020
1021pub mod main_loop {
1049 #![cfg_attr(not(target_arch = "wasm32"), allow(dead_code))]
1051
1052 #[cfg(target_arch = "wasm32")]
1053 mod asyncify {
1054 #[link(wasm_import_module = "asyncify")]
1055 unsafe extern "C" {
1056 pub fn start_unwind(data: *mut u8);
1057 pub fn stop_unwind();
1058 pub fn start_rewind(data: *mut u8);
1059 pub fn stop_rewind();
1060 }
1061 }
1062
1063 const STACK: usize = 1024 * 1024;
1064
1065 #[repr(C)]
1066 struct Data {
1067 cur: *mut u8,
1068 end: *mut u8,
1069 }
1070
1071 static mut DATA: Data = Data { cur: std::ptr::null_mut(), end: std::ptr::null_mut() };
1073 static mut STACK_BUF: [u8; STACK] = [0; STACK];
1074 static mut REWINDING: bool = false;
1075 static mut UNWINDING: bool = false;
1076 static mut STARTED: bool = false;
1077 static mut FINISHED: bool = false;
1078 static mut EXIT_CODE: i32 = 0;
1079 static mut MAIN: Option<fn() -> i32> = None;
1080 static mut FRAMES: u32 = 0;
1081 static mut RUN_MODE: bool = false;
1083
1084 fn wait_impl() {
1085 #[cfg(target_arch = "wasm32")]
1086 unsafe {
1087 if RUN_MODE {
1088 crate::sys::yield_frame();
1089 FRAMES += 1;
1090 return;
1091 }
1092 if REWINDING {
1093 asyncify::stop_rewind();
1094 REWINDING = false;
1095 FRAMES += 1;
1096 return;
1097 }
1098 DATA.cur = (&raw mut STACK_BUF).cast::<u8>();
1099 DATA.end = DATA.cur.add(STACK);
1100 UNWINDING = true;
1101 asyncify::start_unwind((&raw mut DATA).cast::<u8>());
1102 }
1103 }
1104
1105 static mut WAIT: fn() = wait_impl;
1108
1109 pub fn wait_frame() {
1111 if crate::thread::available() {
1112 return crate::thread::wait_frame(); }
1114 let f = unsafe { std::ptr::read_volatile(&raw const WAIT) };
1115 f()
1116 }
1117
1118 pub fn frames() -> u32 {
1120 if crate::thread::available() {
1121 return crate::thread::frames();
1122 }
1123 unsafe { FRAMES }
1124 }
1125
1126 #[doc(hidden)]
1127 pub fn set_main(f: fn() -> i32) {
1128 unsafe { MAIN = Some(f) }
1129 }
1130
1131 #[inline(never)]
1134 fn run_main() {
1135 unsafe {
1136 let rc = MAIN.map_or(0, |f| f());
1137 if !UNWINDING {
1138 EXIT_CODE = rc;
1139 FINISHED = true;
1140 }
1141 }
1142 }
1143
1144 #[doc(hidden)]
1147 pub fn __run() -> i32 {
1148 unsafe {
1149 RUN_MODE = true;
1150 STARTED = true;
1151 MAIN.map_or(0, |f| f())
1152 }
1153 }
1154
1155 #[doc(hidden)]
1160 #[inline(always)]
1161 pub fn __loop_frame() {
1162 unsafe {
1163 if FINISHED {
1164 return;
1165 }
1166 #[cfg(target_arch = "wasm32")]
1167 {
1168 if !STARTED {
1169 STARTED = true;
1170 } else {
1171 REWINDING = true;
1172 asyncify::start_rewind((&raw mut DATA).cast::<u8>());
1173 }
1174 run_main();
1175 if UNWINDING {
1176 asyncify::stop_unwind();
1177 UNWINDING = false;
1178 return;
1179 }
1180 }
1181 #[cfg(not(target_arch = "wasm32"))]
1182 {
1183 STARTED = true;
1184 run_main();
1185 }
1186 if FINISHED {
1187 crate::exit(EXIT_CODE);
1188 }
1189 }
1190 }
1191}
1192
1193#[macro_export]
1198macro_rules! threaded_main_loop {
1199 ($main:path) => {
1200 #[unsafe(no_mangle)]
1201 pub extern "C" fn gasm_abi_version() -> i32 {
1202 $crate::ABI_VERSION
1203 }
1204 #[unsafe(no_mangle)]
1205 pub extern "C" fn gasm_init() -> i32 {
1206 $crate::thread::__set_main($main);
1207 0
1208 }
1209 #[unsafe(no_mangle)]
1210 pub extern "C" fn gasm_frame() {
1211 gasm_loop_frame()
1212 }
1213 #[unsafe(no_mangle)]
1214 #[inline(never)]
1215 pub extern "C" fn gasm_loop_frame() {
1216 $crate::thread::__threaded_loop_frame()
1217 }
1218 };
1219 ($main:path, $exit:path) => {
1220 $crate::threaded_main_loop!($main);
1221 #[unsafe(no_mangle)]
1222 pub extern "C" fn gasm_exit() {
1223 $exit()
1224 }
1225 };
1226}
1227
1228#[macro_export]
1230macro_rules! main_loop {
1231 ($main:path) => {
1232 #[unsafe(no_mangle)]
1233 pub extern "C" fn gasm_abi_version() -> i32 {
1234 $crate::ABI_VERSION
1235 }
1236 #[unsafe(no_mangle)]
1237 pub extern "C" fn gasm_init() -> i32 {
1238 $crate::main_loop::set_main($main);
1239 0
1240 }
1241 #[unsafe(no_mangle)]
1242 pub extern "C" fn gasm_frame() {
1243 gasm_loop_frame()
1244 }
1245 #[unsafe(no_mangle)]
1246 #[inline(never)]
1247 pub extern "C" fn gasm_loop_frame() {
1248 $crate::main_loop::__loop_frame()
1249 }
1250 #[unsafe(no_mangle)]
1251 pub extern "C" fn gasm_run() -> i32 {
1252 $crate::main_loop::__run()
1253 }
1254 };
1255 ($main:path, $exit:path) => {
1256 $crate::main_loop!($main);
1257 #[unsafe(no_mangle)]
1258 pub extern "C" fn gasm_exit() {
1259 $exit()
1260 }
1261 };
1262}
1263
1264#[macro_export]
1272macro_rules! manifest {
1273 ($json:expr) => {
1274 #[cfg(target_arch = "wasm32")]
1275 #[unsafe(link_section = "gasm.manifest")]
1276 #[used]
1277 static __GASM_MANIFEST: [u8; $json.len()] = {
1278 let b = $json.as_bytes();
1279 let mut a = [0u8; $json.len()];
1280 let mut i = 0;
1281 while i < a.len() {
1282 a[i] = b[i];
1283 i += 1;
1284 }
1285 a
1286 };
1287 };
1288}