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 asset(name: &str) -> Option<Vec<u8>> {
118 let n = unsafe { sys::asset_size(name.as_ptr(), name.len() as u32) };
119 if n < 0 {
120 return None;
121 }
122 let mut buf = vec![0u8; n as usize];
123 let got = unsafe { sys::asset_read(name.as_ptr(), name.len() as u32, buf.as_mut_ptr(), n as u32) };
124 buf.truncate(got.max(0) as usize);
125 Some(buf)
126}
127
128pub fn asset_read_at(name: &str, offset: u32, buf: &mut [u8]) -> Option<usize> {
131 let n = unsafe {
132 sys::asset_read_at(name.as_ptr(), name.len() as u32, offset, buf.as_mut_ptr(), buf.len() as u32)
133 };
134 (n >= 0).then_some(n as usize)
135}
136
137pub mod storage {
144 use crate::sys;
145
146 pub fn get(key: &str) -> Option<Vec<u8>> {
147 let n = unsafe { sys::storage_get(key.as_ptr(), key.len() as u32, std::ptr::null_mut(), 0) };
148 if n < 0 {
149 return None;
150 }
151 let mut buf = vec![0u8; n as usize];
152 unsafe { sys::storage_get(key.as_ptr(), key.len() as u32, buf.as_mut_ptr(), n as u32) };
153 Some(buf)
154 }
155
156 pub fn set(key: &str, value: &[u8]) -> bool {
158 unsafe { sys::storage_set(key.as_ptr(), key.len() as u32, value.as_ptr(), value.len() as u32) == 0 }
159 }
160
161 pub fn delete(key: &str) -> bool {
163 unsafe { sys::storage_delete(key.as_ptr(), key.len() as u32) == 0 }
164 }
165}
166
167pub mod gfx {
171 use crate::sys;
172
173 #[derive(Clone, Copy, Debug, PartialEq, Eq)]
174 pub struct Shader(pub u32);
175 #[derive(Clone, Copy, Debug, PartialEq, Eq)]
176 pub struct Buffer(pub u32);
177 #[derive(Clone, Copy, Debug, PartialEq, Eq)]
178 pub struct Pipeline(pub u32);
179 #[derive(Clone, Copy, Debug, PartialEq, Eq)]
180 pub struct BindGroup(pub u32);
181
182 pub const COPY_DST: u32 = 0x08;
183 pub const INDEX: u32 = 0x10;
184 pub const VERTEX: u32 = 0x20;
185 pub const UNIFORM: u32 = 0x40;
186
187 #[derive(Clone, Copy)]
188 pub enum IndexFormat {
189 U16 = 0,
190 U32 = 1,
191 }
192
193 pub unsafe trait Pod: Copy {}
198 unsafe impl Pod for u8 {}
199 unsafe impl Pod for u16 {}
200 unsafe impl Pod for u32 {}
201 unsafe impl Pod for f32 {}
202 unsafe impl<T: Pod, const N: usize> Pod for [T; N] {}
203
204 pub fn as_bytes<T: Pod>(data: &[T]) -> &[u8] {
205 unsafe { std::slice::from_raw_parts(data.as_ptr() as *const u8, std::mem::size_of_val(data)) }
206 }
207
208 pub fn size() -> (u32, u32) {
210 unsafe { (sys::gfx_width(), sys::gfx_height()) }
211 }
212 pub fn create_shader(wgsl: &str) -> Shader {
213 Shader(unsafe { sys::gfx_create_shader(wgsl.as_ptr(), wgsl.len() as u32) })
214 }
215 pub fn create_buffer(size: u32, usage: u32) -> Buffer {
216 Buffer(unsafe { sys::gfx_create_buffer(size, usage) })
217 }
218 pub fn create_pipeline(json: &str) -> Pipeline {
221 Pipeline(unsafe { sys::gfx_create_pipeline(json.as_ptr(), json.len() as u32) })
222 }
223 pub fn create_bind_group(json: &str) -> BindGroup {
225 BindGroup(unsafe { sys::gfx_create_bind_group(json.as_ptr(), json.len() as u32) })
226 }
227 pub fn write_buffer<T: Pod>(buf: Buffer, offset: u32, data: &[T]) {
229 let b = as_bytes(data);
230 unsafe { sys::gfx_write_buffer(buf.0, offset, b.as_ptr(), b.len() as u32) }
231 }
232 pub fn begin_frame(clear: [f32; 4]) -> bool {
234 unsafe { sys::gfx_begin_frame(clear[0], clear[1], clear[2], clear[3]) != 0 }
235 }
236 pub fn set_pipeline(p: Pipeline) {
237 unsafe { sys::gfx_set_pipeline(p.0) }
238 }
239 pub fn set_bind_group(index: u32, bg: BindGroup) {
240 unsafe { sys::gfx_set_bind_group(index, bg.0) }
241 }
242 pub fn set_vertex_buffer(slot: u32, buf: Buffer, offset: u32) {
243 unsafe { sys::gfx_set_vertex_buffer(slot, buf.0, offset) }
244 }
245 pub fn set_index_buffer(buf: Buffer, format: IndexFormat, offset: u32) {
246 unsafe { sys::gfx_set_index_buffer(buf.0, format as u32, offset) }
247 }
248 pub fn draw(vertices: u32, instances: u32, first_vertex: u32, first_instance: u32) {
249 unsafe { sys::gfx_draw(vertices, instances, first_vertex, first_instance) }
250 }
251 pub fn draw_indexed(indices: u32, instances: u32, first_index: u32, base_vertex: i32, first_instance: u32) {
252 unsafe { sys::gfx_draw_indexed(indices, instances, first_index, base_vertex, first_instance) }
253 }
254 pub fn end_frame() {
255 unsafe { sys::gfx_end_frame() }
256 }
257}
258
259pub mod net {
263 use crate::sys;
264
265 #[derive(Clone, Copy, Debug, PartialEq, Eq)]
266 pub enum State {
267 Connecting,
268 Open,
269 Closed,
270 Error,
271 }
272
273 pub enum Recv {
274 Message(Vec<u8>),
275 Empty,
276 Closed,
277 }
278
279 #[derive(Debug)]
280 pub struct Conn(i32);
281
282 impl Conn {
283 pub fn open(url: &str) -> Option<Conn> {
285 let h = unsafe { sys::net_open(url.as_ptr(), url.len() as u32) };
286 (h > 0).then_some(Conn(h))
287 }
288 pub fn state(&self) -> State {
289 match unsafe { sys::net_state(self.0) } {
290 0 => State::Connecting,
291 1 => State::Open,
292 2 => State::Closed,
293 _ => State::Error,
294 }
295 }
296 pub fn send(&self, msg: &[u8]) -> bool {
298 !msg.is_empty() && unsafe { sys::net_send(self.0, msg.as_ptr(), msg.len() as u32) } == 0
299 }
300 pub fn recv(&self) -> Recv {
302 let mut buf = [0u8; 1024];
303 let n = unsafe { sys::net_recv(self.0, buf.as_mut_ptr(), buf.len() as u32) };
304 match n {
305 0 => Recv::Empty,
306 n if n < 0 => Recv::Closed,
307 n if n as usize <= buf.len() => Recv::Message(buf[..n as usize].to_vec()),
308 n => {
309 let mut big = vec![0u8; n as usize];
310 unsafe { sys::net_recv(self.0, big.as_mut_ptr(), n as u32) };
311 Recv::Message(big)
312 }
313 }
314 }
315 }
316
317 impl Drop for Conn {
318 fn drop(&mut self) {
319 unsafe { sys::net_close(self.0) }
320 }
321 }
322}
323
324pub trait Game: Sized + 'static {
328 fn init() -> Result<Self, String>;
329 fn frame(&mut self);
330 fn exit(&mut self) {}
333}
334
335#[doc(hidden)]
336pub struct GameCell<T>(pub UnsafeCell<Option<T>>);
337unsafe impl<T> Sync for GameCell<T> {}
339
340#[doc(hidden)]
341pub fn __init<G: Game>(cell: &GameCell<G>) -> i32 {
342 std::panic::set_hook(Box::new(|info| log(&format!("panic: {info}"))));
343 match G::init() {
344 Ok(g) => {
345 unsafe { *cell.0.get() = Some(g) };
346 0
347 }
348 Err(e) => {
349 log(&format!("init failed: {e}"));
350 1
351 }
352 }
353}
354
355#[doc(hidden)]
356pub fn __frame<G: Game>(cell: &GameCell<G>) {
357 if let Some(g) = unsafe { (*cell.0.get()).as_mut() } {
358 g.frame();
359 }
360}
361
362#[doc(hidden)]
363pub fn __exit<G: Game>(cell: &GameCell<G>) {
364 if let Some(g) = unsafe { (*cell.0.get()).as_mut() } {
365 g.exit();
366 }
367}
368
369#[macro_export]
371macro_rules! game {
372 ($t:ty) => {
373 static __GASM_GAME: $crate::GameCell<$t> = $crate::GameCell(::std::cell::UnsafeCell::new(None));
374
375 #[unsafe(no_mangle)]
376 pub extern "C" fn gasm_abi_version() -> i32 {
377 $crate::ABI_VERSION
378 }
379 #[unsafe(no_mangle)]
380 pub extern "C" fn gasm_init() -> i32 {
381 $crate::__init::<$t>(&__GASM_GAME)
382 }
383 #[unsafe(no_mangle)]
384 pub extern "C" fn gasm_frame() {
385 $crate::__frame::<$t>(&__GASM_GAME)
386 }
387 #[unsafe(no_mangle)]
388 pub extern "C" fn gasm_exit() {
389 $crate::__exit::<$t>(&__GASM_GAME)
390 }
391 };
392}