pub mod sys;
pub mod gles;
pub mod sync;
pub mod thread;
#[cfg(not(target_arch = "wasm32"))]
pub mod native;
use std::cell::UnsafeCell;
pub const ABI_VERSION: i32 = 0;
pub fn log(msg: &str) {
unsafe { sys::log(msg.as_ptr(), msg.len() as u32) }
}
#[macro_export]
macro_rules! log {
($($t:tt)*) => { $crate::log(&::std::format!($($t)*)) };
}
pub fn time_ms() -> f64 {
unsafe { sys::time_ms() }
}
pub fn utc_offset_minutes() -> i32 {
if has("gasm.utc_offset_minutes") { unsafe { sys::utc_offset_minutes() } } else { 0 }
}
pub fn has(name: &str) -> bool {
unsafe { sys::has(name.as_ptr(), name.len() as u32) == 1 }
}
pub fn set_frame_rate(hz: f64) {
unsafe { sys::set_frame_rate(hz) }
}
pub fn video_set_aspect(num: u32, den: u32) -> bool {
static SUPPORTED: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
let ok = *SUPPORTED.get_or_init(|| has("gasm.video_set_aspect"));
if ok {
unsafe { sys::video_set_aspect(num, den) }
}
ok
}
#[macro_export]
macro_rules! title {
($t:literal) => {
#[cfg_attr(target_arch = "wasm32", unsafe(link_section = "gasm.title"))]
#[used]
static __GASM_TITLE: [u8; $t.len()] = $crate::__title_bytes($t);
};
}
#[doc(hidden)]
pub const fn __title_bytes<const N: usize>(s: &str) -> [u8; N] {
let b = s.as_bytes();
let mut out = [0u8; N];
let mut i = 0;
while i < N {
out[i] = b[i];
i += 1;
}
out
}
pub fn set_title(title: &str) {
static SUPPORTED: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
if *SUPPORTED.get_or_init(|| has("gasm.set_title")) {
unsafe { sys::set_title(title.as_ptr(), title.len() as u32) }
}
}
pub fn param(name: &str) -> Option<String> {
let len = unsafe { sys::param(name.as_ptr(), name.len() as u32, std::ptr::null_mut(), 0) };
if len < 0 {
return None;
}
let mut buf = vec![0u8; len as usize];
unsafe { sys::param(name.as_ptr(), name.len() as u32, buf.as_mut_ptr(), len as u32) };
Some(String::from_utf8_lossy(&buf).into_owned())
}
pub fn exit(code: i32) -> ! {
unsafe { sys::proc_exit(code) }
}
pub fn present(rgba: &[u8], width: u32, height: u32, stride: u32) {
let need = (stride as u64)
.checked_mul(height.saturating_sub(1) as u64)
.and_then(|n| n.checked_add(width as u64 * 4))
.filter(|_| width > 0 && height > 0 && stride as u64 >= width as u64 * 4);
assert!(need.is_some_and(|n| rgba.len() as u64 >= n), "present: bad geometry or buffer too small");
unsafe { sys::video_present(rgba.as_ptr(), width, height, stride) }
}
pub mod audio {
use std::sync::atomic::{AtomicU32, Ordering};
static CHANNELS: AtomicU32 = AtomicU32::new(2);
pub fn config(sample_rate: u32, channels: u32) {
if (8000..=192_000).contains(&sample_rate) && (channels == 1 || channels == 2) {
CHANNELS.store(channels, Ordering::Relaxed);
}
unsafe { crate::sys::audio_config(sample_rate, channels) }
}
pub fn push(samples: &[f32]) {
let ch = CHANNELS.load(Ordering::Relaxed);
debug_assert!(samples.len() % ch as usize == 0, "audio::push: not a whole number of frames");
unsafe { crate::sys::audio_push(samples.as_ptr(), samples.len() as u32 / ch) }
}
}
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
pub struct Buttons(pub u32);
impl Buttons {
pub const A: u32 = sys::GASM_BTN_A;
pub const B: u32 = sys::GASM_BTN_B;
pub const X: u32 = sys::GASM_BTN_X;
pub const Y: u32 = sys::GASM_BTN_Y;
pub const L: u32 = sys::GASM_BTN_L;
pub const R: u32 = sys::GASM_BTN_R;
pub const SELECT: u32 = sys::GASM_BTN_SELECT;
pub const START: u32 = sys::GASM_BTN_START;
pub const UP: u32 = sys::GASM_BTN_UP;
pub const DOWN: u32 = sys::GASM_BTN_DOWN;
pub const LEFT: u32 = sys::GASM_BTN_LEFT;
pub const RIGHT: u32 = sys::GASM_BTN_RIGHT;
pub fn held(self, mask: u32) -> bool {
self.0 & mask != 0
}
}
pub fn pad(player: u32) -> Buttons {
Buttons(unsafe { sys::input_pad(player) })
}
pub fn text_input() -> Option<String> {
let len = unsafe { sys::text_input(std::ptr::null_mut(), 0) };
if len < 0 {
return None;
}
let mut buf = vec![0u8; len as usize];
if len > 0 {
unsafe { sys::text_input(buf.as_mut_ptr(), len as u32) };
}
Some(String::from_utf8_lossy(&buf).into_owned())
}
pub fn asset_names() -> Vec<String> {
let n = unsafe { sys::asset_count() };
(0..n)
.filter_map(|i| {
let len = unsafe { sys::asset_name(i, std::ptr::null_mut(), 0) };
if len < 0 {
return None;
}
let mut buf = vec![0u8; len as usize];
if len > 0 {
unsafe { sys::asset_name(i, buf.as_mut_ptr(), len as u32) };
}
Some(String::from_utf8_lossy(&buf).into_owned())
})
.collect()
}
pub fn asset_size(name: &str) -> Option<u64> {
let n = unsafe { sys::asset_size64(name.as_ptr(), name.len() as u32) };
(n >= 0).then_some(n as u64)
}
pub fn asset_version(name: &str) -> Option<u32> {
if !has("gasm.asset_version") {
return asset_size(name).map(|_| 0);
}
let v = unsafe { sys::asset_version(name.as_ptr(), name.len() as u32) };
(v >= 0).then_some(v as u32)
}
pub fn asset(name: &str) -> Option<Vec<u8>> {
let n = unsafe { sys::asset_size(name.as_ptr(), name.len() as u32) };
if n < 0 {
return None;
}
let mut buf = vec![0u8; n as usize];
let got = unsafe { sys::asset_read(name.as_ptr(), name.len() as u32, buf.as_mut_ptr(), n as u32) };
buf.truncate(got.max(0) as usize);
Some(buf)
}
pub fn asset_read_at(name: &str, offset: u64, buf: &mut [u8]) -> Option<usize> {
let len = buf.len().min(u32::MAX as usize) as u32;
let n = unsafe { sys::asset_read_at64(name.as_ptr(), name.len() as u32, offset, buf.as_mut_ptr(), len) };
(n >= 0).then_some(n as usize)
}
pub mod storage {
use crate::sys;
pub fn get(key: &str) -> Option<Vec<u8>> {
let n = unsafe { sys::storage_get(key.as_ptr(), key.len() as u32, std::ptr::null_mut(), 0) };
if n < 0 {
return None;
}
let mut buf = vec![0u8; n as usize];
unsafe { sys::storage_get(key.as_ptr(), key.len() as u32, buf.as_mut_ptr(), n as u32) };
Some(buf)
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum Error {
Key,
Size,
Quota,
Io,
}
pub fn try_set(key: &str, value: &[u8]) -> Result<(), Error> {
match unsafe { sys::storage_set(key.as_ptr(), key.len() as u32, value.as_ptr(), value.len() as u32) } {
sys::GASM_STORAGE_OK => Ok(()),
sys::GASM_STORAGE_ERR_KEY => Err(Error::Key),
sys::GASM_STORAGE_ERR_SIZE => Err(Error::Size),
sys::GASM_STORAGE_ERR_QUOTA => Err(Error::Quota),
_ => Err(Error::Io),
}
}
pub fn set(key: &str, value: &[u8]) -> bool {
try_set(key, value).is_ok()
}
pub fn delete(key: &str) -> bool {
unsafe { sys::storage_delete(key.as_ptr(), key.len() as u32) == 0 }
}
pub fn keys() -> Vec<String> {
let n = unsafe { sys::storage_count() };
(0..n)
.filter_map(|i| {
let len = unsafe { sys::storage_key(i, std::ptr::null_mut(), 0) };
if len < 0 {
return None;
}
let mut buf = vec![0u8; len as usize];
unsafe { sys::storage_key(i, buf.as_mut_ptr(), len as u32) };
Some(String::from_utf8_lossy(&buf).into_owned())
})
.collect()
}
}
pub mod input {
use crate::sys;
pub const KEYS_RAW: u32 = sys::GASM_INPUT_KEYS_RAW;
pub const POINTER_HIDDEN: u32 = sys::GASM_INPUT_POINTER_HIDDEN;
pub const POINTER_LOCKED: u32 = sys::GASM_INPUT_POINTER_LOCKED;
pub const MOUSE_LEFT: u32 = sys::GASM_MOUSE_LEFT;
pub const MOUSE_RIGHT: u32 = sys::GASM_MOUSE_RIGHT;
pub const MOUSE_MIDDLE: u32 = sys::GASM_MOUSE_MIDDLE;
pub const MOUSE_BACK: u32 = sys::GASM_MOUSE_BACK;
pub const MOUSE_FORWARD: u32 = sys::GASM_MOUSE_FORWARD;
const KEY_BYTES: usize = sys::GASM_KEY_STATE_BYTES as usize;
const POINTER_BYTES: usize = sys::GASM_POINTER_BYTES as usize;
const GAMEPAD_BYTES: usize = sys::GASM_GAMEPAD_BYTES as usize;
pub fn set_mode(flags: u32) {
unsafe { sys::input_mode(flags) }
}
#[derive(Clone, Copy, Default, PartialEq, Eq)]
pub struct Keys(pub [u8; KEY_BYTES]);
impl Keys {
pub fn held(&self, code: u32) -> bool {
(code as usize) < KEY_BYTES * 8 && self.0[code as usize / 8] & (1 << (code % 8)) != 0
}
}
pub fn keys() -> Option<Keys> {
let mut k = Keys::default();
(unsafe { sys::key_state(k.0.as_mut_ptr(), KEY_BYTES as u32) } >= 0).then_some(k)
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct KeyEvent {
pub code: u32,
pub down: bool,
}
pub fn key_events() -> Option<Vec<KeyEvent>> {
let n = unsafe { sys::key_events(std::ptr::null_mut(), 0) };
if n < 0 {
return None;
}
let mut b = vec![0u8; n as usize];
if n > 0 {
unsafe { sys::key_events(b.as_mut_ptr(), n as u32) };
}
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())
}
#[derive(Clone, Copy, Debug, Default, PartialEq)]
pub struct Pointer {
pub x: f32,
pub y: f32,
pub frame_x: f32,
pub frame_y: f32,
pub dx: f32,
pub dy: f32,
pub wheel_x: f32,
pub wheel_y: f32,
pub buttons: u32,
pub pressed: u32,
pub released: u32,
pub flags: u32,
}
impl Pointer {
pub fn inside(&self) -> bool {
self.flags & 1 != 0
}
pub fn locked(&self) -> bool {
self.flags & 4 != 0
}
}
pub fn pointer() -> Option<Pointer> {
let mut b = [0u8; POINTER_BYTES];
if unsafe { sys::pointer(b.as_mut_ptr(), POINTER_BYTES as u32) } < 0 {
return None;
}
let f = |o: u32| { let i = o as usize; f32::from_le_bytes([b[i], b[i + 1], b[i + 2], b[i + 3]]) };
let u = |o: u32| { let i = o as usize; u32::from_le_bytes([b[i], b[i + 1], b[i + 2], b[i + 3]]) };
Some(Pointer {
x: f(sys::GASM_POINTER_OFF_X), y: f(sys::GASM_POINTER_OFF_Y),
frame_x: f(sys::GASM_POINTER_OFF_FX), frame_y: f(sys::GASM_POINTER_OFF_FY),
dx: f(sys::GASM_POINTER_OFF_DX), dy: f(sys::GASM_POINTER_OFF_DY),
wheel_x: f(sys::GASM_POINTER_OFF_WHEEL_X), wheel_y: f(sys::GASM_POINTER_OFF_WHEEL_Y),
buttons: u(sys::GASM_POINTER_OFF_BUTTONS), pressed: u(sys::GASM_POINTER_OFF_PRESSED),
released: u(sys::GASM_POINTER_OFF_RELEASED), flags: u(sys::GASM_POINTER_OFF_FLAGS),
})
}
#[derive(Clone, Debug, Default, PartialEq)]
pub struct Gamepad {
pub connected: bool,
pub standard: bool,
pub buttons: Vec<f32>,
pub axes: Vec<f32>,
}
pub fn gamepad(slot: u32) -> Option<Gamepad> {
let mut b = [0u8; GAMEPAD_BYTES];
if unsafe { sys::gamepad(slot, b.as_mut_ptr(), GAMEPAD_BYTES as u32) } < 0 {
return None;
}
let u = |i: usize| u32::from_le_bytes([b[i], b[i + 1], b[i + 2], b[i + 3]]);
let f = |i: usize| f32::from_le_bytes([b[i], b[i + 1], b[i + 2], b[i + 3]]);
let (flags, nb, na) = (
u(sys::GASM_GAMEPAD_OFF_FLAGS as usize),
(u(sys::GASM_GAMEPAD_OFF_BUTTON_COUNT as usize) as usize).min(sys::GASM_GAMEPAD_BUTTONS as usize),
(u(sys::GASM_GAMEPAD_OFF_AXIS_COUNT as usize) as usize).min(sys::GASM_GAMEPAD_AXES as usize),
);
Some(Gamepad {
connected: flags & sys::GASM_GAMEPAD_CONNECTED != 0,
standard: flags & sys::GASM_GAMEPAD_STANDARD != 0,
buttons: (0..nb).map(|i| f(sys::GASM_GAMEPAD_OFF_BUTTONS as usize + i * 4)).collect(),
axes: (0..na).map(|i| f(sys::GASM_GAMEPAD_OFF_AXES as usize + i * 4)).collect(),
})
}
pub fn gamepad_name(slot: u32) -> Option<String> {
let n = unsafe { sys::gamepad_name(slot, std::ptr::null_mut(), 0) };
if n < 0 {
return None;
}
let mut b = vec![0u8; n as usize];
if n > 0 {
unsafe { sys::gamepad_name(slot, b.as_mut_ptr(), n as u32) };
}
Some(String::from_utf8_lossy(&b).into_owned())
}
}
pub use sys::keys;
pub mod gfx {
use crate::sys;
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct Shader(pub u32);
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct Buffer(pub u32);
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct Pipeline(pub u32);
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct BindGroup(pub u32);
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct BindGroupLayout(pub u32);
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct Texture(pub u32);
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct Sampler(pub u32);
pub const COPY_DST: u32 = sys::GASM_BUF_COPY_DST;
pub const INDEX: u32 = sys::GASM_BUF_INDEX;
pub const VERTEX: u32 = sys::GASM_BUF_VERTEX;
pub const UNIFORM: u32 = sys::GASM_BUF_UNIFORM;
pub const STORAGE: u32 = sys::GASM_BUF_STORAGE;
pub const STAGE_VERTEX: u32 = sys::GASM_STAGE_VERTEX;
pub const STAGE_FRAGMENT: u32 = sys::GASM_STAGE_FRAGMENT;
pub const OFFSET_ALIGNMENT: u32 = 256;
#[derive(Clone, Copy)]
pub enum IndexFormat {
U16 = sys::GASM_INDEX_U16 as isize,
U32 = sys::GASM_INDEX_U32 as isize,
}
pub trait Handle {
fn raw(&self) -> u32;
}
macro_rules! handles {
($($t:ident),*) => { $(impl Handle for $t { fn raw(&self) -> u32 { self.0 } })* };
}
handles!(Shader, Buffer, Pipeline, BindGroup, BindGroupLayout, Texture, Sampler);
pub unsafe trait Pod: Copy {}
unsafe impl Pod for u8 {}
unsafe impl Pod for u16 {}
unsafe impl Pod for u32 {}
unsafe impl Pod for f32 {}
unsafe impl<T: Pod, const N: usize> Pod for [T; N] {}
pub fn as_bytes<T: Pod>(data: &[T]) -> &[u8] {
unsafe { std::slice::from_raw_parts(data.as_ptr() as *const u8, std::mem::size_of_val(data)) }
}
pub fn size() -> (u32, u32) {
unsafe { (sys::gfx_width(), sys::gfx_height()) }
}
pub fn create_shader(wgsl: &str) -> Shader {
Shader(unsafe { sys::gfx_create_shader(wgsl.as_ptr(), wgsl.len() as u32) })
}
pub fn create_buffer(size: u32, usage: u32) -> Buffer {
Buffer(unsafe { sys::gfx_create_buffer(size, usage) })
}
pub fn create_pipeline(json: &str) -> Pipeline {
Pipeline(unsafe { sys::gfx_create_pipeline(json.as_ptr(), json.len() as u32) })
}
pub fn create_bind_group(json: &str) -> BindGroup {
BindGroup(unsafe { sys::gfx_create_bind_group(json.as_ptr(), json.len() as u32) })
}
pub fn create_bind_group_layout(json: &str) -> BindGroupLayout {
BindGroupLayout(unsafe { sys::gfx_create_bind_group_layout(json.as_ptr(), json.len() as u32) })
}
pub fn create_texture(json: &str) -> Texture {
Texture(unsafe { sys::gfx_create_texture(json.as_ptr(), json.len() as u32) })
}
pub fn write_texture(t: Texture, mip: u32, x: u32, y: u32, w: u32, h: u32, rgba: &[u8]) {
unsafe { sys::gfx_write_texture(t.0, mip, x, y, w, h, rgba.as_ptr(), rgba.len() as u32) }
}
pub fn create_sampler(json: &str) -> Sampler {
Sampler(unsafe { sys::gfx_create_sampler(json.as_ptr(), json.len() as u32) })
}
pub fn write_buffer<T: Pod>(buf: Buffer, offset: u32, data: &[T]) {
let b = as_bytes(data);
unsafe { sys::gfx_write_buffer(buf.0, offset, b.as_ptr(), b.len() as u32) }
}
pub fn begin_frame(clear: [f32; 4]) -> bool {
unsafe { sys::gfx_begin_frame(clear[0], clear[1], clear[2], clear[3]) != 0 }
}
pub fn set_pipeline(p: Pipeline) {
unsafe { sys::gfx_set_pipeline(p.0) }
}
pub fn set_bind_group(index: u32, bg: BindGroup) {
unsafe { sys::gfx_set_bind_group(index, bg.0) }
}
pub fn set_bind_group_offsets(index: u32, bg: BindGroup, offsets: &[u32]) {
unsafe { sys::gfx_set_bind_group_offsets(index, bg.0, offsets.as_ptr(), offsets.len() as u32) }
}
pub fn set_viewport(x: f32, y: f32, w: f32, h: f32, min_depth: f32, max_depth: f32) {
unsafe { sys::gfx_set_viewport(x, y, w, h, min_depth, max_depth) }
}
pub fn set_scissor_rect(x: u32, y: u32, w: u32, h: u32) {
unsafe { sys::gfx_set_scissor_rect(x, y, w, h) }
}
pub fn set_vertex_buffer(slot: u32, buf: Buffer, offset: u32) {
unsafe { sys::gfx_set_vertex_buffer(slot, buf.0, offset) }
}
pub fn set_index_buffer(buf: Buffer, format: IndexFormat, offset: u32) {
unsafe { sys::gfx_set_index_buffer(buf.0, format as u32, offset) }
}
pub fn draw(vertices: u32, instances: u32, first_vertex: u32, first_instance: u32) {
unsafe { sys::gfx_draw(vertices, instances, first_vertex, first_instance) }
}
pub fn draw_indexed(indices: u32, instances: u32, first_index: u32, base_vertex: i32, first_instance: u32) {
unsafe { sys::gfx_draw_indexed(indices, instances, first_index, base_vertex, first_instance) }
}
pub fn end_frame() {
unsafe { sys::gfx_end_frame() }
}
pub fn destroy(h: impl Handle) {
unsafe { sys::gfx_destroy(h.raw()) }
}
}
pub mod net {
use crate::sys;
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum State {
Connecting,
Open,
Closed,
Error,
}
pub enum Recv {
Message(Vec<u8>),
Empty,
Closed,
}
#[derive(Debug)]
pub struct Conn(i32);
impl Conn {
pub fn open(url: &str) -> Option<Conn> {
let h = unsafe { sys::net_open(url.as_ptr(), url.len() as u32) };
(h > 0).then(|| Conn(h)) }
pub fn state(&self) -> State {
match unsafe { sys::net_state(self.0) } {
0 => State::Connecting,
1 => State::Open,
2 => State::Closed,
_ => State::Error,
}
}
pub fn send(&self, msg: &[u8]) -> bool {
!msg.is_empty() && unsafe { sys::net_send(self.0, msg.as_ptr(), msg.len() as u32) } == 0
}
pub fn recv(&self) -> Recv {
let mut buf = [0u8; 1024];
let n = unsafe { sys::net_recv(self.0, buf.as_mut_ptr(), buf.len() as u32) };
match n {
0 => Recv::Empty,
n if n < 0 => Recv::Closed,
n if n as usize <= buf.len() => Recv::Message(buf[..n as usize].to_vec()),
n => {
let mut big = vec![0u8; n as usize];
unsafe { sys::net_recv(self.0, big.as_mut_ptr(), n as u32) };
Recv::Message(big)
}
}
}
}
impl Drop for Conn {
fn drop(&mut self) {
unsafe { sys::net_close(self.0) }
}
}
}
pub mod fetch {
use crate::sys;
pub fn available() -> bool {
crate::has("gasm:fetch")
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum State {
Pending,
Headers,
Done,
Failed,
}
#[derive(Clone, Debug)]
pub struct Request {
method: String,
url: String,
headers: Vec<(String, String)>,
body: Vec<u8>,
}
fn json_str(s: &str, out: &mut String) {
out.push('"');
for c in s.chars() {
match c {
'"' => out.push_str("\\\""),
'\\' => out.push_str("\\\\"),
c if (c as u32) < 0x20 => out.push_str(&format!("\\u{:04x}", c as u32)),
c => out.push(c),
}
}
out.push('"');
}
impl Request {
pub fn new(method: &str, url: &str) -> Request {
Request { method: method.to_owned(), url: url.to_owned(), headers: Vec::new(), body: Vec::new() }
}
pub fn get(url: &str) -> Request {
Request::new("GET", url)
}
pub fn post(url: &str, body: impl Into<Vec<u8>>) -> Request {
Request::new("POST", url).body(body)
}
pub fn header(mut self, name: &str, value: &str) -> Request {
self.headers.push((name.to_owned(), value.to_owned()));
self
}
pub fn body(mut self, body: impl Into<Vec<u8>>) -> Request {
self.body = body.into();
self
}
pub fn describe(&self) -> String {
let mut d = String::from("{\"method\":");
json_str(&self.method, &mut d);
d.push_str(",\"url\":");
json_str(&self.url, &mut d);
d.push_str(",\"headers\":{");
for (i, (k, v)) in self.headers.iter().enumerate() {
if i > 0 {
d.push(',');
}
json_str(k, &mut d);
d.push(':');
json_str(v, &mut d);
}
d.push_str("}}");
d
}
pub fn send(&self) -> Option<Response> {
if !available() {
return None;
}
let d = self.describe();
let h = unsafe { sys::fetch_request(d.as_ptr(), d.len() as u32, self.body.as_ptr(), self.body.len() as u32) };
(h > 0).then(|| Response(h))
}
}
#[derive(Debug)]
pub struct Response(i32);
impl Response {
pub fn state(&self) -> State {
match unsafe { sys::fetch_state(self.0) } {
0 => State::Pending,
1 => State::Headers,
2 => State::Done,
_ => State::Failed,
}
}
pub fn status(&self) -> i32 {
unsafe { sys::fetch_status(self.0) }
}
pub fn headers(&self) -> Option<Vec<(String, String)>> {
let n = unsafe { sys::fetch_headers(self.0, std::ptr::null_mut(), 0) };
if n < 0 {
return None;
}
let mut buf = vec![0u8; n as usize];
unsafe { sys::fetch_headers(self.0, buf.as_mut_ptr(), n as u32) };
let text = String::from_utf8_lossy(&buf);
Some(text.lines().filter_map(|l| l.split_once(": ").map(|(k, v)| (k.to_owned(), v.to_owned()))).collect())
}
pub fn header(&self, name: &str) -> Option<String> {
self.headers()?.into_iter().find(|(k, _)| k == name).map(|(_, v)| v)
}
pub fn read(&mut self, buf: &mut [u8]) -> Option<usize> {
let n = unsafe { sys::fetch_read(self.0, buf.as_mut_ptr(), buf.len() as u32) };
(n >= 0).then_some(n as usize)
}
pub fn read_to_end(&mut self) -> Vec<u8> {
let mut out = Vec::new();
let mut buf = vec![0u8; 64 * 1024];
while let Some(n @ 1..) = self.read(&mut buf) {
out.extend_from_slice(&buf[..n]);
}
out
}
}
impl Drop for Response {
fn drop(&mut self) {
unsafe { sys::fetch_close(self.0) }
}
}
}
pub trait Game: Sized + 'static {
fn init() -> Result<Self, String>;
fn frame(&mut self);
fn exit(&mut self) {}
}
#[doc(hidden)]
pub struct GameCell<T>(pub UnsafeCell<Option<T>>);
#[cfg(target_arch = "wasm32")]
unsafe impl<T> Sync for GameCell<T> {}
#[cfg(not(target_arch = "wasm32"))]
unsafe impl<T: Send> Sync for GameCell<T> {}
#[doc(hidden)]
pub unsafe fn __init<G: Game>(cell: &GameCell<G>) -> i32 {
std::panic::set_hook(Box::new(|info| log(&format!("panic: {info}"))));
match G::init() {
Ok(g) => {
unsafe { *cell.0.get() = Some(g) };
0
}
Err(e) => {
log(&format!("init failed: {e}"));
1
}
}
}
#[doc(hidden)]
pub unsafe fn __frame<G: Game>(cell: &GameCell<G>) {
if let Some(g) = unsafe { (*cell.0.get()).as_mut() } {
g.frame();
}
}
#[doc(hidden)]
pub unsafe fn __exit<G: Game>(cell: &GameCell<G>) {
if let Some(g) = unsafe { (*cell.0.get()).as_mut() } {
g.exit();
}
}
#[macro_export]
macro_rules! game {
($t:ty) => {
static __GASM_GAME: $crate::GameCell<$t> = $crate::GameCell(::std::cell::UnsafeCell::new(None));
#[unsafe(no_mangle)]
pub extern "C" fn gasm_abi_version() -> i32 {
$crate::ABI_VERSION
}
#[unsafe(no_mangle)]
pub extern "C" fn gasm_init() -> i32 {
unsafe { $crate::__init::<$t>(&__GASM_GAME) }
}
#[unsafe(no_mangle)]
pub extern "C" fn gasm_frame() {
unsafe { $crate::__frame::<$t>(&__GASM_GAME) }
}
#[unsafe(no_mangle)]
pub extern "C" fn gasm_exit() {
unsafe { $crate::__exit::<$t>(&__GASM_GAME) }
}
};
}
pub mod main_loop {
#![cfg_attr(not(target_arch = "wasm32"), allow(dead_code))]
#[cfg(target_arch = "wasm32")]
mod asyncify {
#[link(wasm_import_module = "asyncify")]
unsafe extern "C" {
pub fn start_unwind(data: *mut u8);
pub fn stop_unwind();
pub fn start_rewind(data: *mut u8);
pub fn stop_rewind();
}
}
const STACK: usize = 1024 * 1024;
#[repr(C)]
struct Data {
cur: *mut u8,
end: *mut u8,
}
static mut DATA: Data = Data { cur: std::ptr::null_mut(), end: std::ptr::null_mut() };
static mut STACK_BUF: [u8; STACK] = [0; STACK];
static mut REWINDING: bool = false;
static mut UNWINDING: bool = false;
static mut STARTED: bool = false;
static mut FINISHED: bool = false;
static mut EXIT_CODE: i32 = 0;
static mut MAIN: Option<fn() -> i32> = None;
static mut FRAMES: u32 = 0;
static mut RUN_MODE: bool = false;
fn wait_impl() {
#[cfg(target_arch = "wasm32")]
unsafe {
if RUN_MODE {
crate::sys::yield_frame();
FRAMES += 1;
return;
}
if REWINDING {
asyncify::stop_rewind();
REWINDING = false;
FRAMES += 1;
return;
}
DATA.cur = (&raw mut STACK_BUF).cast::<u8>();
DATA.end = DATA.cur.add(STACK);
UNWINDING = true;
asyncify::start_unwind((&raw mut DATA).cast::<u8>());
}
}
static mut WAIT: fn() = wait_impl;
pub fn wait_frame() {
if crate::thread::available() {
return crate::thread::wait_frame(); }
let f = unsafe { std::ptr::read_volatile(&raw const WAIT) };
f()
}
pub fn frames() -> u32 {
if crate::thread::available() {
return crate::thread::frames();
}
unsafe { FRAMES }
}
#[doc(hidden)]
pub fn set_main(f: fn() -> i32) {
unsafe { MAIN = Some(f) }
}
#[inline(never)]
fn run_main() {
unsafe {
let rc = MAIN.map_or(0, |f| f());
if !UNWINDING {
EXIT_CODE = rc;
FINISHED = true;
}
}
}
#[doc(hidden)]
pub fn __run() -> i32 {
unsafe {
RUN_MODE = true;
STARTED = true;
MAIN.map_or(0, |f| f())
}
}
#[doc(hidden)]
#[inline(always)]
pub fn __loop_frame() {
unsafe {
if FINISHED {
return;
}
#[cfg(target_arch = "wasm32")]
{
if !STARTED {
STARTED = true;
} else {
REWINDING = true;
asyncify::start_rewind((&raw mut DATA).cast::<u8>());
}
run_main();
if UNWINDING {
asyncify::stop_unwind();
UNWINDING = false;
return;
}
}
#[cfg(not(target_arch = "wasm32"))]
{
STARTED = true;
run_main();
}
if FINISHED {
crate::exit(EXIT_CODE);
}
}
}
}
#[macro_export]
macro_rules! threaded_main_loop {
($main:path) => {
#[unsafe(no_mangle)]
pub extern "C" fn gasm_abi_version() -> i32 {
$crate::ABI_VERSION
}
#[unsafe(no_mangle)]
pub extern "C" fn gasm_init() -> i32 {
$crate::thread::__set_main($main);
0
}
#[unsafe(no_mangle)]
pub extern "C" fn gasm_frame() {
gasm_loop_frame()
}
#[unsafe(no_mangle)]
#[inline(never)]
pub extern "C" fn gasm_loop_frame() {
$crate::thread::__threaded_loop_frame()
}
};
($main:path, $exit:path) => {
$crate::threaded_main_loop!($main);
#[unsafe(no_mangle)]
pub extern "C" fn gasm_exit() {
$exit()
}
};
}
#[macro_export]
macro_rules! main_loop {
($main:path) => {
#[unsafe(no_mangle)]
pub extern "C" fn gasm_abi_version() -> i32 {
$crate::ABI_VERSION
}
#[unsafe(no_mangle)]
pub extern "C" fn gasm_init() -> i32 {
$crate::main_loop::set_main($main);
0
}
#[unsafe(no_mangle)]
pub extern "C" fn gasm_frame() {
gasm_loop_frame()
}
#[unsafe(no_mangle)]
#[inline(never)]
pub extern "C" fn gasm_loop_frame() {
$crate::main_loop::__loop_frame()
}
#[unsafe(no_mangle)]
pub extern "C" fn gasm_run() -> i32 {
$crate::main_loop::__run()
}
};
($main:path, $exit:path) => {
$crate::main_loop!($main);
#[unsafe(no_mangle)]
pub extern "C" fn gasm_exit() {
$exit()
}
};
}