1use std::sync::Mutex;
10
11struct State {
12 frame_rate: f64,
13 frames: u64,
14 assets: Vec<(String, Vec<u8>)>,
15 params: Vec<(String, String)>,
16 storage: Vec<(String, Vec<u8>)>,
17 pads: [u32; 4],
18 channels: u32,
19 hashing: bool,
20 video_hash: u32,
21 audio_hash: u32,
22 presented: u64,
23 size: (u32, u32),
24 audio_frames: u64,
25 next_handle: u32,
26}
27
28static STATE: Mutex<State> = Mutex::new(State {
29 frame_rate: 60.0,
30 frames: 0,
31 assets: Vec::new(),
32 params: Vec::new(),
33 storage: Vec::new(),
34 pads: [0; 4],
35 channels: 2,
36 hashing: true,
37 video_hash: FNV_INIT,
38 audio_hash: FNV_INIT,
39 presented: 0,
40 size: (0, 0),
41 audio_frames: 0,
42 next_handle: 1,
43});
44
45const FNV_INIT: u32 = 0x811c_9dc5;
46fn fnv(mut h: u32, bytes: &[u8]) -> u32 {
47 for &b in bytes {
48 h = (h ^ b as u32).wrapping_mul(0x0100_0193);
49 }
50 h
51}
52
53fn with<R>(f: impl FnOnce(&mut State) -> R) -> R {
54 f(&mut STATE.lock().unwrap())
55}
56
57pub fn set_asset(name: &str, bytes: Vec<u8>) {
59 with(|s| s.assets.push((name.into(), bytes)));
60}
61pub fn set_param(name: &str, value: &str) {
62 with(|s| s.params.push((name.into(), value.into())));
63}
64pub fn set_pad(player: usize, buttons: u32) {
65 with(|s| s.pads[player] = buttons);
66}
67pub fn set_hashing(on: bool) {
68 with(|s| s.hashing = on);
69}
70pub fn end_frame() {
72 with(|s| s.frames += 1);
73}
74
75pub struct Stats {
76 pub frame_rate: f64,
77 pub presented: u64,
78 pub width: u32,
79 pub height: u32,
80 pub video_hash: u32,
81 pub audio_hash: u32,
82 pub audio_frames: u64,
83}
84
85pub fn stats() -> Stats {
86 with(|s| Stats {
87 frame_rate: s.frame_rate,
88 presented: s.presented,
89 width: s.size.0,
90 height: s.size.1,
91 video_hash: s.video_hash,
92 audio_hash: s.audio_hash,
93 audio_frames: s.audio_frames,
94 })
95}
96
97#[allow(clippy::missing_safety_doc)]
99pub mod abi {
100 use super::*;
101
102 unsafe fn bytes<'a>(ptr: *const u8, len: u32) -> &'a [u8] {
103 if len == 0 { &[] } else { unsafe { std::slice::from_raw_parts(ptr, len as usize) } }
104 }
105 unsafe fn text(ptr: *const u8, len: u32) -> String {
106 String::from_utf8_lossy(unsafe { bytes(ptr, len) }).into_owned()
107 }
108 unsafe fn copy_out(src: &[u8], dst: *mut u8, cap: u32) -> i32 {
109 if src.len() <= cap as usize && !src.is_empty() {
110 unsafe { std::ptr::copy_nonoverlapping(src.as_ptr(), dst, src.len()) };
111 }
112 src.len() as i32
113 }
114
115 pub unsafe fn log(ptr: *const u8, len: u32) {
116 eprintln!("[guest] {}", unsafe { text(ptr, len) });
117 }
118 pub unsafe fn time_ms() -> f64 {
119 with(|s| s.frames as f64 * 1000.0 / s.frame_rate)
120 }
121 pub unsafe fn set_frame_rate(hz: f64) {
122 with(|s| s.frame_rate = hz);
123 }
124 pub unsafe fn video_present(ptr: *const u8, w: u32, h: u32, stride: u32) {
125 with(|s| {
126 if s.hashing {
127 for y in 0..h {
128 let row = unsafe { bytes(ptr.add((y * stride) as usize), w * 4) };
129 s.video_hash = fnv(s.video_hash, row);
130 }
131 }
132 s.size = (w, h);
133 s.presented += 1;
134 })
135 }
136 pub unsafe fn audio_config(_rate: u32, channels: u32) {
137 with(|s| s.channels = channels);
138 }
139 pub unsafe fn audio_push(ptr: *const f32, frames: u32) {
140 with(|s| {
141 if s.hashing {
142 let b = unsafe { bytes(ptr as *const u8, frames * s.channels * 4) };
143 s.audio_hash = fnv(s.audio_hash, b);
144 }
145 s.audio_frames += frames as u64;
146 })
147 }
148 pub unsafe fn input_pad(player: u32) -> u32 {
149 with(|s| s.pads.get(player as usize).copied().unwrap_or(0))
150 }
151 pub unsafe fn asset_size(name: *const u8, len: u32) -> i32 {
152 let n = unsafe { text(name, len) };
153 with(|s| s.assets.iter().find(|a| a.0 == n).map_or(-1, |a| a.1.len() as i32))
154 }
155 pub unsafe fn asset_read(name: *const u8, len: u32, dst: *mut u8, cap: u32) -> i32 {
156 let n = unsafe { text(name, len) };
157 with(|s| match s.assets.iter().find(|a| a.0 == n) {
158 Some(a) => {
159 let k = a.1.len().min(cap as usize);
160 unsafe { std::ptr::copy_nonoverlapping(a.1.as_ptr(), dst, k) };
161 k as i32
162 }
163 None => -1,
164 })
165 }
166 pub unsafe fn asset_read_at(name: *const u8, len: u32, offset: u32, dst: *mut u8, cap: u32) -> i32 {
167 let n = unsafe { text(name, len) };
168 with(|s| match s.assets.iter().find(|a| a.0 == n) {
169 Some(a) => {
170 let start = (offset as usize).min(a.1.len());
171 let k = (a.1.len() - start).min(cap as usize);
172 unsafe { std::ptr::copy_nonoverlapping(a.1[start..].as_ptr(), dst, k) };
173 k as i32
174 }
175 None => -1,
176 })
177 }
178
179 pub unsafe fn storage_get(key: *const u8, len: u32, dst: *mut u8, cap: u32) -> i32 {
181 let k = unsafe { text(key, len) };
182 with(|s| match s.storage.iter().find(|e| e.0 == k) {
183 Some(e) => unsafe { copy_out(&e.1, dst, cap) },
184 None => -1,
185 })
186 }
187 pub unsafe fn storage_set(key: *const u8, len: u32, data: *const u8, data_len: u32) -> i32 {
188 let k = unsafe { text(key, len) };
189 let v = unsafe { bytes(data, data_len) }.to_vec();
190 with(|s| {
191 s.storage.retain(|e| e.0 != k);
192 s.storage.push((k, v));
193 0
194 })
195 }
196 pub unsafe fn storage_delete(key: *const u8, len: u32) -> i32 {
197 let k = unsafe { text(key, len) };
198 with(|s| {
199 let before = s.storage.len();
200 s.storage.retain(|e| e.0 != k);
201 if s.storage.len() < before { 0 } else { -1 }
202 })
203 }
204
205 pub unsafe fn param(name: *const u8, len: u32, dst: *mut u8, cap: u32) -> i32 {
206 let n = unsafe { text(name, len) };
207 with(|s| match s.params.iter().find(|p| p.0 == n) {
208 Some(p) => unsafe { copy_out(p.1.as_bytes(), dst, cap) },
209 None => -1,
210 })
211 }
212
213 fn handle() -> u32 {
215 with(|s| {
216 s.next_handle += 1;
217 s.next_handle - 1
218 })
219 }
220 pub unsafe fn gfx_width() -> u32 { 1280 }
221 pub unsafe fn gfx_height() -> u32 { 720 }
222 pub unsafe fn gfx_create_shader(_: *const u8, _: u32) -> u32 { handle() }
223 pub unsafe fn gfx_create_buffer(_: u32, _: u32) -> u32 { handle() }
224 pub unsafe fn gfx_create_pipeline(_: *const u8, _: u32) -> u32 { handle() }
225 pub unsafe fn gfx_create_bind_group(_: *const u8, _: u32) -> u32 { handle() }
226 pub unsafe fn gfx_write_buffer(_: u32, _: u32, ptr: *const u8, len: u32) {
227 with(|s| {
228 if s.hashing {
229 s.video_hash = fnv(s.video_hash, unsafe { bytes(ptr, len) });
230 }
231 })
232 }
233 pub unsafe fn gfx_begin_frame(_: f32, _: f32, _: f32, _: f32) -> u32 { 0 }
234 pub unsafe fn gfx_set_pipeline(_: u32) {}
235 pub unsafe fn gfx_set_bind_group(_: u32, _: u32) {}
236 pub unsafe fn gfx_set_vertex_buffer(_: u32, _: u32, _: u32) {}
237 pub unsafe fn gfx_set_index_buffer(_: u32, _: u32, _: u32) {}
238 pub unsafe fn gfx_draw(_: u32, _: u32, _: u32, _: u32) {}
239 pub unsafe fn gfx_draw_indexed(_: u32, _: u32, _: u32, _: i32, _: u32) {}
240 pub unsafe fn gfx_end_frame() {
241 with(|s| s.presented += 1);
242 }
243
244 pub unsafe fn net_open(_: *const u8, _: u32) -> i32 { -1 }
246 pub unsafe fn net_state(_: i32) -> u32 { 3 }
247 pub unsafe fn net_send(_: i32, _: *const u8, _: u32) -> i32 { -1 }
248 pub unsafe fn net_recv(_: i32, _: *mut u8, _: u32) -> i32 { -1 }
249 pub unsafe fn net_close(_: i32) {}
250
251 pub unsafe fn proc_exit(code: i32) -> ! {
252 std::process::exit(code)
253 }
254}