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