1pub mod sys;
17
18#[cfg(not(target_arch = "wasm32"))]
19pub mod native;
20
21use std::cell::UnsafeCell;
22
23pub const ABI_VERSION: i32 = 0;
24
25pub fn log(msg: &str) {
28 unsafe { sys::log(msg.as_ptr(), msg.len() as u32) }
29}
30
31#[macro_export]
33macro_rules! log {
34 ($($t:tt)*) => { $crate::log(&::std::format!($($t)*)) };
35}
36
37pub fn time_ms() -> f64 {
39 unsafe { sys::time_ms() }
40}
41
42pub fn set_frame_rate(hz: f64) {
44 unsafe { sys::set_frame_rate(hz) }
45}
46
47pub fn param(name: &str) -> Option<String> {
49 let len = unsafe { sys::param(name.as_ptr(), name.len() as u32, std::ptr::null_mut(), 0) };
50 if len < 0 {
51 return None;
52 }
53 let mut buf = vec![0u8; len as usize];
54 unsafe { sys::param(name.as_ptr(), name.len() as u32, buf.as_mut_ptr(), len as u32) };
55 Some(String::from_utf8_lossy(&buf).into_owned())
56}
57
58pub fn exit(code: i32) -> ! {
60 unsafe { sys::proc_exit(code) }
61}
62
63pub fn present(rgba: &[u8], width: u32, height: u32, stride: u32) {
67 assert!(rgba.len() >= (stride * (height - 1) + width * 4) as usize, "present: buffer too small");
68 unsafe { sys::video_present(rgba.as_ptr(), width, height, stride) }
69}
70
71pub mod audio {
72 use std::sync::atomic::{AtomicU32, Ordering};
73 static CHANNELS: AtomicU32 = AtomicU32::new(2);
74
75 pub fn config(sample_rate: u32, channels: u32) {
77 CHANNELS.store(channels, Ordering::Relaxed);
78 unsafe { crate::sys::audio_config(sample_rate, channels) }
79 }
80
81 pub fn push(samples: &[f32]) {
83 let ch = CHANNELS.load(Ordering::Relaxed).max(1);
84 unsafe { crate::sys::audio_push(samples.as_ptr(), samples.len() as u32 / ch) }
85 }
86}
87
88#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
90pub struct Buttons(pub u32);
91
92impl Buttons {
93 pub const A: u32 = 1 << 0;
94 pub const B: u32 = 1 << 1;
95 pub const X: u32 = 1 << 2;
96 pub const Y: u32 = 1 << 3;
97 pub const L: u32 = 1 << 4;
98 pub const R: u32 = 1 << 5;
99 pub const SELECT: u32 = 1 << 6;
100 pub const START: u32 = 1 << 7;
101 pub const UP: u32 = 1 << 8;
102 pub const DOWN: u32 = 1 << 9;
103 pub const LEFT: u32 = 1 << 10;
104 pub const RIGHT: u32 = 1 << 11;
105
106 pub fn held(self, mask: u32) -> bool {
107 self.0 & mask != 0
108 }
109}
110
111pub fn pad(player: u32) -> Buttons {
113 Buttons(unsafe { sys::input_pad(player) })
114}
115
116pub fn text_input() -> Option<String> {
119 let len = unsafe { sys::text_input(std::ptr::null_mut(), 0) };
120 if len < 0 {
121 return None;
122 }
123 let mut buf = vec![0u8; len as usize];
124 if len > 0 {
125 unsafe { sys::text_input(buf.as_mut_ptr(), len as u32) };
126 }
127 Some(String::from_utf8_lossy(&buf).into_owned())
128}
129
130pub fn asset_names() -> Vec<String> {
132 let n = unsafe { sys::asset_count() };
133 (0..n)
134 .filter_map(|i| {
135 let len = unsafe { sys::asset_name(i, std::ptr::null_mut(), 0) };
136 if len < 0 {
137 return None;
138 }
139 let mut buf = vec![0u8; len as usize];
140 if len > 0 {
141 unsafe { sys::asset_name(i, buf.as_mut_ptr(), len as u32) };
142 }
143 Some(String::from_utf8_lossy(&buf).into_owned())
144 })
145 .collect()
146}
147
148pub fn asset(name: &str) -> Option<Vec<u8>> {
150 let n = unsafe { sys::asset_size(name.as_ptr(), name.len() as u32) };
151 if n < 0 {
152 return None;
153 }
154 let mut buf = vec![0u8; n as usize];
155 let got = unsafe { sys::asset_read(name.as_ptr(), name.len() as u32, buf.as_mut_ptr(), n as u32) };
156 buf.truncate(got.max(0) as usize);
157 Some(buf)
158}
159
160pub fn asset_read_at(name: &str, offset: u32, buf: &mut [u8]) -> Option<usize> {
163 let n = unsafe {
164 sys::asset_read_at(name.as_ptr(), name.len() as u32, offset, buf.as_mut_ptr(), buf.len() as u32)
165 };
166 (n >= 0).then_some(n as usize)
167}
168
169pub mod storage {
176 use crate::sys;
177
178 pub fn get(key: &str) -> Option<Vec<u8>> {
179 let n = unsafe { sys::storage_get(key.as_ptr(), key.len() as u32, std::ptr::null_mut(), 0) };
180 if n < 0 {
181 return None;
182 }
183 let mut buf = vec![0u8; n as usize];
184 unsafe { sys::storage_get(key.as_ptr(), key.len() as u32, buf.as_mut_ptr(), n as u32) };
185 Some(buf)
186 }
187
188 pub fn set(key: &str, value: &[u8]) -> bool {
190 unsafe { sys::storage_set(key.as_ptr(), key.len() as u32, value.as_ptr(), value.len() as u32) == 0 }
191 }
192
193 pub fn delete(key: &str) -> bool {
195 unsafe { sys::storage_delete(key.as_ptr(), key.len() as u32) == 0 }
196 }
197
198 pub fn keys() -> Vec<String> {
200 let n = unsafe { sys::storage_count() };
201 (0..n)
202 .filter_map(|i| {
203 let len = unsafe { sys::storage_key(i, std::ptr::null_mut(), 0) };
204 if len < 0 {
205 return None;
206 }
207 let mut buf = vec![0u8; len as usize];
208 unsafe { sys::storage_key(i, buf.as_mut_ptr(), len as u32) };
209 Some(String::from_utf8_lossy(&buf).into_owned())
210 })
211 .collect()
212 }
213}
214
215pub mod input {
227 use crate::sys;
228
229 pub const KEYS_RAW: u32 = 1 << 0;
231 pub const POINTER_HIDDEN: u32 = 1 << 1;
233 pub const POINTER_LOCKED: u32 = 1 << 2;
235
236 pub const MOUSE_LEFT: u32 = 1 << 0;
237 pub const MOUSE_RIGHT: u32 = 1 << 1;
238 pub const MOUSE_MIDDLE: u32 = 1 << 2;
239 pub const MOUSE_BACK: u32 = 1 << 3;
240 pub const MOUSE_FORWARD: u32 = 1 << 4;
241
242 pub fn set_mode(flags: u32) {
244 unsafe { sys::input_mode(flags) }
245 }
246
247 #[derive(Clone, Copy, Default, PartialEq, Eq)]
249 pub struct Keys(pub [u8; 32]);
250
251 impl Keys {
252 pub fn held(&self, code: u32) -> bool {
253 (code as usize) < 256 && self.0[code as usize / 8] & (1 << (code % 8)) != 0
254 }
255 }
256
257 pub fn keys() -> Option<Keys> {
259 let mut k = Keys::default();
260 (unsafe { sys::key_state(k.0.as_mut_ptr(), 32) } >= 0).then_some(k)
261 }
262
263 #[derive(Clone, Copy, Debug, PartialEq, Eq)]
264 pub struct KeyEvent {
265 pub code: u32,
266 pub down: bool,
267 }
268
269 pub fn key_events() -> Option<Vec<KeyEvent>> {
271 let n = unsafe { sys::key_events(std::ptr::null_mut(), 0) };
272 if n < 0 {
273 return None;
274 }
275 let mut b = vec![0u8; n as usize];
276 if n > 0 {
277 unsafe { sys::key_events(b.as_mut_ptr(), n as u32) };
278 }
279 Some(b.chunks_exact(4).map(|e| KeyEvent { code: u16::from_le_bytes([e[0], e[1]]) as u32, down: e[2] != 0 }).collect())
280 }
281
282 #[derive(Clone, Copy, Debug, Default, PartialEq)]
283 pub struct Pointer {
284 pub x: f32,
286 pub y: f32,
287 pub frame_x: f32,
289 pub frame_y: f32,
290 pub dx: f32,
292 pub dy: f32,
293 pub wheel_x: f32,
295 pub wheel_y: f32,
296 pub buttons: u32,
297 pub pressed: u32,
299 pub released: u32,
300 pub flags: u32,
301 }
302
303 impl Pointer {
304 pub fn inside(&self) -> bool {
305 self.flags & 1 != 0
306 }
307 pub fn locked(&self) -> bool {
308 self.flags & 4 != 0
309 }
310 }
311
312 pub fn pointer() -> Option<Pointer> {
314 let mut b = [0u8; 48];
315 if unsafe { sys::pointer(b.as_mut_ptr(), 48) } < 0 {
316 return None;
317 }
318 let f = |i: usize| f32::from_le_bytes([b[i], b[i + 1], b[i + 2], b[i + 3]]);
319 let u = |i: usize| u32::from_le_bytes([b[i], b[i + 1], b[i + 2], b[i + 3]]);
320 Some(Pointer {
321 x: f(0), y: f(4), frame_x: f(8), frame_y: f(12), dx: f(16), dy: f(20), wheel_x: f(24), wheel_y: f(28),
322 buttons: u(32), pressed: u(36), released: u(40), flags: u(44),
323 })
324 }
325
326 #[derive(Clone, Debug, Default, PartialEq)]
330 pub struct Gamepad {
331 pub connected: bool,
332 pub standard: bool,
333 pub buttons: Vec<f32>,
334 pub axes: Vec<f32>,
335 }
336
337 pub fn gamepad(slot: u32) -> Option<Gamepad> {
339 let mut b = [0u8; 204];
340 if unsafe { sys::gamepad(slot, b.as_mut_ptr(), 204) } < 0 {
341 return None;
342 }
343 let u = |i: usize| u32::from_le_bytes([b[i], b[i + 1], b[i + 2], b[i + 3]]);
344 let f = |i: usize| f32::from_le_bytes([b[i], b[i + 1], b[i + 2], b[i + 3]]);
345 let (flags, nb, na) = (u(0), (u(4) as usize).min(32), (u(8) as usize).min(16));
346 Some(Gamepad {
347 connected: flags & 1 != 0,
348 standard: flags & 2 != 0,
349 buttons: (0..nb).map(|i| f(12 + i * 4)).collect(),
350 axes: (0..na).map(|i| f(140 + i * 4)).collect(),
351 })
352 }
353
354 pub fn gamepad_name(slot: u32) -> Option<String> {
356 let n = unsafe { sys::gamepad_name(slot, std::ptr::null_mut(), 0) };
357 if n < 0 {
358 return None;
359 }
360 let mut b = vec![0u8; n as usize];
361 if n > 0 {
362 unsafe { sys::gamepad_name(slot, b.as_mut_ptr(), n as u32) };
363 }
364 Some(String::from_utf8_lossy(&b).into_owned())
365 }
366}
367
368pub use sys::keys;
370
371pub mod gfx {
375 use crate::sys;
376
377 #[derive(Clone, Copy, Debug, PartialEq, Eq)]
378 pub struct Shader(pub u32);
379 #[derive(Clone, Copy, Debug, PartialEq, Eq)]
380 pub struct Buffer(pub u32);
381 #[derive(Clone, Copy, Debug, PartialEq, Eq)]
382 pub struct Pipeline(pub u32);
383 #[derive(Clone, Copy, Debug, PartialEq, Eq)]
384 pub struct BindGroup(pub u32);
385 #[derive(Clone, Copy, Debug, PartialEq, Eq)]
386 pub struct BindGroupLayout(pub u32);
387 #[derive(Clone, Copy, Debug, PartialEq, Eq)]
388 pub struct Texture(pub u32);
389 #[derive(Clone, Copy, Debug, PartialEq, Eq)]
390 pub struct Sampler(pub u32);
391
392 pub const COPY_DST: u32 = 0x08;
393 pub const INDEX: u32 = 0x10;
394 pub const VERTEX: u32 = 0x20;
395 pub const UNIFORM: u32 = 0x40;
396 pub const STORAGE: u32 = 0x80;
397
398 pub const STAGE_VERTEX: u32 = 0x1;
400 pub const STAGE_FRAGMENT: u32 = 0x2;
401 pub const OFFSET_ALIGNMENT: u32 = 256;
403
404 #[derive(Clone, Copy)]
405 pub enum IndexFormat {
406 U16 = 0,
407 U32 = 1,
408 }
409
410 pub unsafe trait Pod: Copy {}
415 unsafe impl Pod for u8 {}
416 unsafe impl Pod for u16 {}
417 unsafe impl Pod for u32 {}
418 unsafe impl Pod for f32 {}
419 unsafe impl<T: Pod, const N: usize> Pod for [T; N] {}
420
421 pub fn as_bytes<T: Pod>(data: &[T]) -> &[u8] {
422 unsafe { std::slice::from_raw_parts(data.as_ptr() as *const u8, std::mem::size_of_val(data)) }
423 }
424
425 pub fn size() -> (u32, u32) {
427 unsafe { (sys::gfx_width(), sys::gfx_height()) }
428 }
429 pub fn create_shader(wgsl: &str) -> Shader {
430 Shader(unsafe { sys::gfx_create_shader(wgsl.as_ptr(), wgsl.len() as u32) })
431 }
432 pub fn create_buffer(size: u32, usage: u32) -> Buffer {
433 Buffer(unsafe { sys::gfx_create_buffer(size, usage) })
434 }
435 pub fn create_pipeline(json: &str) -> Pipeline {
438 Pipeline(unsafe { sys::gfx_create_pipeline(json.as_ptr(), json.len() as u32) })
439 }
440 pub fn create_bind_group(json: &str) -> BindGroup {
443 BindGroup(unsafe { sys::gfx_create_bind_group(json.as_ptr(), json.len() as u32) })
444 }
445 pub fn create_bind_group_layout(json: &str) -> BindGroupLayout {
448 BindGroupLayout(unsafe { sys::gfx_create_bind_group_layout(json.as_ptr(), json.len() as u32) })
449 }
450 pub fn create_texture(json: &str) -> Texture {
452 Texture(unsafe { sys::gfx_create_texture(json.as_ptr(), json.len() as u32) })
453 }
454 pub fn write_texture(t: Texture, mip: u32, x: u32, y: u32, w: u32, h: u32, rgba: &[u8]) {
456 unsafe { sys::gfx_write_texture(t.0, mip, x, y, w, h, rgba.as_ptr(), rgba.len() as u32) }
457 }
458 pub fn create_sampler(json: &str) -> Sampler {
460 Sampler(unsafe { sys::gfx_create_sampler(json.as_ptr(), json.len() as u32) })
461 }
462 pub fn write_buffer<T: Pod>(buf: Buffer, offset: u32, data: &[T]) {
464 let b = as_bytes(data);
465 unsafe { sys::gfx_write_buffer(buf.0, offset, b.as_ptr(), b.len() as u32) }
466 }
467 pub fn begin_frame(clear: [f32; 4]) -> bool {
469 unsafe { sys::gfx_begin_frame(clear[0], clear[1], clear[2], clear[3]) != 0 }
470 }
471 pub fn set_pipeline(p: Pipeline) {
472 unsafe { sys::gfx_set_pipeline(p.0) }
473 }
474 pub fn set_bind_group(index: u32, bg: BindGroup) {
475 unsafe { sys::gfx_set_bind_group(index, bg.0) }
476 }
477 pub fn set_bind_group_offsets(index: u32, bg: BindGroup, offsets: &[u32]) {
479 unsafe { sys::gfx_set_bind_group_offsets(index, bg.0, offsets.as_ptr(), offsets.len() as u32) }
480 }
481 pub fn set_viewport(x: f32, y: f32, w: f32, h: f32, min_depth: f32, max_depth: f32) {
483 unsafe { sys::gfx_set_viewport(x, y, w, h, min_depth, max_depth) }
484 }
485 pub fn set_scissor_rect(x: u32, y: u32, w: u32, h: u32) {
487 unsafe { sys::gfx_set_scissor_rect(x, y, w, h) }
488 }
489 pub fn set_vertex_buffer(slot: u32, buf: Buffer, offset: u32) {
490 unsafe { sys::gfx_set_vertex_buffer(slot, buf.0, offset) }
491 }
492 pub fn set_index_buffer(buf: Buffer, format: IndexFormat, offset: u32) {
493 unsafe { sys::gfx_set_index_buffer(buf.0, format as u32, offset) }
494 }
495 pub fn draw(vertices: u32, instances: u32, first_vertex: u32, first_instance: u32) {
496 unsafe { sys::gfx_draw(vertices, instances, first_vertex, first_instance) }
497 }
498 pub fn draw_indexed(indices: u32, instances: u32, first_index: u32, base_vertex: i32, first_instance: u32) {
499 unsafe { sys::gfx_draw_indexed(indices, instances, first_index, base_vertex, first_instance) }
500 }
501 pub fn end_frame() {
502 unsafe { sys::gfx_end_frame() }
503 }
504}
505
506pub mod net {
510 use crate::sys;
511
512 #[derive(Clone, Copy, Debug, PartialEq, Eq)]
513 pub enum State {
514 Connecting,
515 Open,
516 Closed,
517 Error,
518 }
519
520 pub enum Recv {
521 Message(Vec<u8>),
522 Empty,
523 Closed,
524 }
525
526 #[derive(Debug)]
527 pub struct Conn(i32);
528
529 impl Conn {
530 pub fn open(url: &str) -> Option<Conn> {
532 let h = unsafe { sys::net_open(url.as_ptr(), url.len() as u32) };
533 (h > 0).then_some(Conn(h))
534 }
535 pub fn state(&self) -> State {
536 match unsafe { sys::net_state(self.0) } {
537 0 => State::Connecting,
538 1 => State::Open,
539 2 => State::Closed,
540 _ => State::Error,
541 }
542 }
543 pub fn send(&self, msg: &[u8]) -> bool {
545 !msg.is_empty() && unsafe { sys::net_send(self.0, msg.as_ptr(), msg.len() as u32) } == 0
546 }
547 pub fn recv(&self) -> Recv {
549 let mut buf = [0u8; 1024];
550 let n = unsafe { sys::net_recv(self.0, buf.as_mut_ptr(), buf.len() as u32) };
551 match n {
552 0 => Recv::Empty,
553 n if n < 0 => Recv::Closed,
554 n if n as usize <= buf.len() => Recv::Message(buf[..n as usize].to_vec()),
555 n => {
556 let mut big = vec![0u8; n as usize];
557 unsafe { sys::net_recv(self.0, big.as_mut_ptr(), n as u32) };
558 Recv::Message(big)
559 }
560 }
561 }
562 }
563
564 impl Drop for Conn {
565 fn drop(&mut self) {
566 unsafe { sys::net_close(self.0) }
567 }
568 }
569}
570
571pub trait Game: Sized + 'static {
575 fn init() -> Result<Self, String>;
576 fn frame(&mut self);
577 fn exit(&mut self) {}
580}
581
582#[doc(hidden)]
583pub struct GameCell<T>(pub UnsafeCell<Option<T>>);
584unsafe impl<T> Sync for GameCell<T> {}
586
587#[doc(hidden)]
588pub fn __init<G: Game>(cell: &GameCell<G>) -> i32 {
589 std::panic::set_hook(Box::new(|info| log(&format!("panic: {info}"))));
590 match G::init() {
591 Ok(g) => {
592 unsafe { *cell.0.get() = Some(g) };
593 0
594 }
595 Err(e) => {
596 log(&format!("init failed: {e}"));
597 1
598 }
599 }
600}
601
602#[doc(hidden)]
603pub fn __frame<G: Game>(cell: &GameCell<G>) {
604 if let Some(g) = unsafe { (*cell.0.get()).as_mut() } {
605 g.frame();
606 }
607}
608
609#[doc(hidden)]
610pub fn __exit<G: Game>(cell: &GameCell<G>) {
611 if let Some(g) = unsafe { (*cell.0.get()).as_mut() } {
612 g.exit();
613 }
614}
615
616#[macro_export]
618macro_rules! game {
619 ($t:ty) => {
620 static __GASM_GAME: $crate::GameCell<$t> = $crate::GameCell(::std::cell::UnsafeCell::new(None));
621
622 #[unsafe(no_mangle)]
623 pub extern "C" fn gasm_abi_version() -> i32 {
624 $crate::ABI_VERSION
625 }
626 #[unsafe(no_mangle)]
627 pub extern "C" fn gasm_init() -> i32 {
628 $crate::__init::<$t>(&__GASM_GAME)
629 }
630 #[unsafe(no_mangle)]
631 pub extern "C" fn gasm_frame() {
632 $crate::__frame::<$t>(&__GASM_GAME)
633 }
634 #[unsafe(no_mangle)]
635 pub extern "C" fn gasm_exit() {
636 $crate::__exit::<$t>(&__GASM_GAME)
637 }
638 };
639}