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
199pub mod gfx {
203 use crate::sys;
204
205 #[derive(Clone, Copy, Debug, PartialEq, Eq)]
206 pub struct Shader(pub u32);
207 #[derive(Clone, Copy, Debug, PartialEq, Eq)]
208 pub struct Buffer(pub u32);
209 #[derive(Clone, Copy, Debug, PartialEq, Eq)]
210 pub struct Pipeline(pub u32);
211 #[derive(Clone, Copy, Debug, PartialEq, Eq)]
212 pub struct BindGroup(pub u32);
213 #[derive(Clone, Copy, Debug, PartialEq, Eq)]
214 pub struct BindGroupLayout(pub u32);
215 #[derive(Clone, Copy, Debug, PartialEq, Eq)]
216 pub struct Texture(pub u32);
217 #[derive(Clone, Copy, Debug, PartialEq, Eq)]
218 pub struct Sampler(pub u32);
219
220 pub const COPY_DST: u32 = 0x08;
221 pub const INDEX: u32 = 0x10;
222 pub const VERTEX: u32 = 0x20;
223 pub const UNIFORM: u32 = 0x40;
224 pub const STORAGE: u32 = 0x80;
225
226 pub const STAGE_VERTEX: u32 = 0x1;
228 pub const STAGE_FRAGMENT: u32 = 0x2;
229 pub const OFFSET_ALIGNMENT: u32 = 256;
231
232 #[derive(Clone, Copy)]
233 pub enum IndexFormat {
234 U16 = 0,
235 U32 = 1,
236 }
237
238 pub unsafe trait Pod: Copy {}
243 unsafe impl Pod for u8 {}
244 unsafe impl Pod for u16 {}
245 unsafe impl Pod for u32 {}
246 unsafe impl Pod for f32 {}
247 unsafe impl<T: Pod, const N: usize> Pod for [T; N] {}
248
249 pub fn as_bytes<T: Pod>(data: &[T]) -> &[u8] {
250 unsafe { std::slice::from_raw_parts(data.as_ptr() as *const u8, std::mem::size_of_val(data)) }
251 }
252
253 pub fn size() -> (u32, u32) {
255 unsafe { (sys::gfx_width(), sys::gfx_height()) }
256 }
257 pub fn create_shader(wgsl: &str) -> Shader {
258 Shader(unsafe { sys::gfx_create_shader(wgsl.as_ptr(), wgsl.len() as u32) })
259 }
260 pub fn create_buffer(size: u32, usage: u32) -> Buffer {
261 Buffer(unsafe { sys::gfx_create_buffer(size, usage) })
262 }
263 pub fn create_pipeline(json: &str) -> Pipeline {
266 Pipeline(unsafe { sys::gfx_create_pipeline(json.as_ptr(), json.len() as u32) })
267 }
268 pub fn create_bind_group(json: &str) -> BindGroup {
271 BindGroup(unsafe { sys::gfx_create_bind_group(json.as_ptr(), json.len() as u32) })
272 }
273 pub fn create_bind_group_layout(json: &str) -> BindGroupLayout {
276 BindGroupLayout(unsafe { sys::gfx_create_bind_group_layout(json.as_ptr(), json.len() as u32) })
277 }
278 pub fn create_texture(json: &str) -> Texture {
280 Texture(unsafe { sys::gfx_create_texture(json.as_ptr(), json.len() as u32) })
281 }
282 pub fn write_texture(t: Texture, mip: u32, x: u32, y: u32, w: u32, h: u32, rgba: &[u8]) {
284 unsafe { sys::gfx_write_texture(t.0, mip, x, y, w, h, rgba.as_ptr(), rgba.len() as u32) }
285 }
286 pub fn create_sampler(json: &str) -> Sampler {
288 Sampler(unsafe { sys::gfx_create_sampler(json.as_ptr(), json.len() as u32) })
289 }
290 pub fn write_buffer<T: Pod>(buf: Buffer, offset: u32, data: &[T]) {
292 let b = as_bytes(data);
293 unsafe { sys::gfx_write_buffer(buf.0, offset, b.as_ptr(), b.len() as u32) }
294 }
295 pub fn begin_frame(clear: [f32; 4]) -> bool {
297 unsafe { sys::gfx_begin_frame(clear[0], clear[1], clear[2], clear[3]) != 0 }
298 }
299 pub fn set_pipeline(p: Pipeline) {
300 unsafe { sys::gfx_set_pipeline(p.0) }
301 }
302 pub fn set_bind_group(index: u32, bg: BindGroup) {
303 unsafe { sys::gfx_set_bind_group(index, bg.0) }
304 }
305 pub fn set_bind_group_offsets(index: u32, bg: BindGroup, offsets: &[u32]) {
307 unsafe { sys::gfx_set_bind_group_offsets(index, bg.0, offsets.as_ptr(), offsets.len() as u32) }
308 }
309 pub fn set_viewport(x: f32, y: f32, w: f32, h: f32, min_depth: f32, max_depth: f32) {
311 unsafe { sys::gfx_set_viewport(x, y, w, h, min_depth, max_depth) }
312 }
313 pub fn set_scissor_rect(x: u32, y: u32, w: u32, h: u32) {
315 unsafe { sys::gfx_set_scissor_rect(x, y, w, h) }
316 }
317 pub fn set_vertex_buffer(slot: u32, buf: Buffer, offset: u32) {
318 unsafe { sys::gfx_set_vertex_buffer(slot, buf.0, offset) }
319 }
320 pub fn set_index_buffer(buf: Buffer, format: IndexFormat, offset: u32) {
321 unsafe { sys::gfx_set_index_buffer(buf.0, format as u32, offset) }
322 }
323 pub fn draw(vertices: u32, instances: u32, first_vertex: u32, first_instance: u32) {
324 unsafe { sys::gfx_draw(vertices, instances, first_vertex, first_instance) }
325 }
326 pub fn draw_indexed(indices: u32, instances: u32, first_index: u32, base_vertex: i32, first_instance: u32) {
327 unsafe { sys::gfx_draw_indexed(indices, instances, first_index, base_vertex, first_instance) }
328 }
329 pub fn end_frame() {
330 unsafe { sys::gfx_end_frame() }
331 }
332}
333
334pub mod net {
338 use crate::sys;
339
340 #[derive(Clone, Copy, Debug, PartialEq, Eq)]
341 pub enum State {
342 Connecting,
343 Open,
344 Closed,
345 Error,
346 }
347
348 pub enum Recv {
349 Message(Vec<u8>),
350 Empty,
351 Closed,
352 }
353
354 #[derive(Debug)]
355 pub struct Conn(i32);
356
357 impl Conn {
358 pub fn open(url: &str) -> Option<Conn> {
360 let h = unsafe { sys::net_open(url.as_ptr(), url.len() as u32) };
361 (h > 0).then_some(Conn(h))
362 }
363 pub fn state(&self) -> State {
364 match unsafe { sys::net_state(self.0) } {
365 0 => State::Connecting,
366 1 => State::Open,
367 2 => State::Closed,
368 _ => State::Error,
369 }
370 }
371 pub fn send(&self, msg: &[u8]) -> bool {
373 !msg.is_empty() && unsafe { sys::net_send(self.0, msg.as_ptr(), msg.len() as u32) } == 0
374 }
375 pub fn recv(&self) -> Recv {
377 let mut buf = [0u8; 1024];
378 let n = unsafe { sys::net_recv(self.0, buf.as_mut_ptr(), buf.len() as u32) };
379 match n {
380 0 => Recv::Empty,
381 n if n < 0 => Recv::Closed,
382 n if n as usize <= buf.len() => Recv::Message(buf[..n as usize].to_vec()),
383 n => {
384 let mut big = vec![0u8; n as usize];
385 unsafe { sys::net_recv(self.0, big.as_mut_ptr(), n as u32) };
386 Recv::Message(big)
387 }
388 }
389 }
390 }
391
392 impl Drop for Conn {
393 fn drop(&mut self) {
394 unsafe { sys::net_close(self.0) }
395 }
396 }
397}
398
399pub trait Game: Sized + 'static {
403 fn init() -> Result<Self, String>;
404 fn frame(&mut self);
405 fn exit(&mut self) {}
408}
409
410#[doc(hidden)]
411pub struct GameCell<T>(pub UnsafeCell<Option<T>>);
412unsafe impl<T> Sync for GameCell<T> {}
414
415#[doc(hidden)]
416pub fn __init<G: Game>(cell: &GameCell<G>) -> i32 {
417 std::panic::set_hook(Box::new(|info| log(&format!("panic: {info}"))));
418 match G::init() {
419 Ok(g) => {
420 unsafe { *cell.0.get() = Some(g) };
421 0
422 }
423 Err(e) => {
424 log(&format!("init failed: {e}"));
425 1
426 }
427 }
428}
429
430#[doc(hidden)]
431pub fn __frame<G: Game>(cell: &GameCell<G>) {
432 if let Some(g) = unsafe { (*cell.0.get()).as_mut() } {
433 g.frame();
434 }
435}
436
437#[doc(hidden)]
438pub fn __exit<G: Game>(cell: &GameCell<G>) {
439 if let Some(g) = unsafe { (*cell.0.get()).as_mut() } {
440 g.exit();
441 }
442}
443
444#[macro_export]
446macro_rules! game {
447 ($t:ty) => {
448 static __GASM_GAME: $crate::GameCell<$t> = $crate::GameCell(::std::cell::UnsafeCell::new(None));
449
450 #[unsafe(no_mangle)]
451 pub extern "C" fn gasm_abi_version() -> i32 {
452 $crate::ABI_VERSION
453 }
454 #[unsafe(no_mangle)]
455 pub extern "C" fn gasm_init() -> i32 {
456 $crate::__init::<$t>(&__GASM_GAME)
457 }
458 #[unsafe(no_mangle)]
459 pub extern "C" fn gasm_frame() {
460 $crate::__frame::<$t>(&__GASM_GAME)
461 }
462 #[unsafe(no_mangle)]
463 pub extern "C" fn gasm_exit() {
464 $crate::__exit::<$t>(&__GASM_GAME)
465 }
466 };
467}