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