1use crate::color::FromRGB;
2use crate::config::{FullID, RuntimeConfig};
3use crate::error::Error;
4use crate::frame_buffer::RenderFB;
5use crate::linking::populate_externals;
6use crate::state::{NetHandler, State};
7use crate::stats::StatsTracker;
8use crate::utils::read_all;
9use alloc::boxed::Box;
10use alloc::vec::Vec;
11use embedded_graphics::draw_target::DrawTarget;
12use embedded_graphics::geometry::OriginDimensions;
13use embedded_graphics::pixelcolor::RgbColor;
14use embedded_io::Read;
15use firefly_hal::*;
16use firefly_types::*;
17
18const FPS: u8 = 60;
20const KB: u32 = 1024;
21const FUEL_PER_CALL: u64 = 10_000_000;
22
23pub struct Runtime<'a, D, C>
24where
25 D: DrawTarget<Color = C> + RenderFB + OriginDimensions,
26 C: RgbColor + FromRGB,
27{
28 display: D,
29 instance: wasmi::Instance,
30 store: wasmi::Store<Box<State<'a>>>,
31
32 update: Option<wasmi::TypedFunc<(), ()>>,
33 render: Option<wasmi::TypedFunc<(), ()>>,
34 before_exit: Option<wasmi::TypedFunc<(), ()>>,
35 cheat: Option<wasmi::TypedFunc<(i32, i32), (i32,)>>,
36 handle_menu: Option<wasmi::TypedFunc<(u32,), ()>>,
37
38 per_frame: Duration,
40 prev_time: Instant,
42 prev_lag: Duration,
44 n_frames: u8,
45 lagging_frames: u8,
46 fast_frames: u8,
47 render_every: u8,
48
49 stats: Option<StatsTracker>,
50 serial: SerialImpl,
51}
52
53impl<'a, D, C> Runtime<'a, D, C>
54where
55 D: DrawTarget<Color = C> + RenderFB + OriginDimensions,
56 C: RgbColor + FromRGB,
57{
58 pub fn new(mut config: RuntimeConfig<'a, D, C>) -> Result<Self, Error> {
60 let id = match config.id {
61 Some(id) => id,
62 None => match detect_launcher(&mut config.device) {
63 Some(id) => id,
64 None => return Err(Error::NoLauncher),
65 },
66 };
67 id.validate()?;
68
69 let rom_path = &["roms", id.author(), id.app()];
70 let mut rom_dir = match config.device.open_dir(rom_path) {
71 Ok(dir) => dir,
72 Err(err) => return Err(Error::OpenDir(rom_path.join("/"), err)),
73 };
74 let file = match rom_dir.open_file("_meta") {
75 Ok(file) => file,
76 Err(err) => return Err(Error::OpenFile("_meta", err)),
77 };
78 let bytes = match read_all(file) {
79 Ok(bytes) => bytes,
80 Err(err) => return Err(Error::ReadFile("_meta", err.into())),
81 };
82 let meta = match Meta::decode(&bytes[..]) {
83 Ok(meta) => meta,
84 Err(err) => return Err(Error::DecodeMeta(err)),
85 };
86 if meta.author_id != id.author() {
87 return Err(Error::AuthorIDMismatch);
88 }
89 if meta.app_id != id.app() {
90 return Err(Error::AppIDMismatch);
91 }
92 let sudo = meta.sudo;
93 let launcher = meta.launcher;
94
95 let mut serial = config.device.serial();
96 let res = serial.start();
97 if let Err(err) = res {
98 return Err(Error::SerialStart(err));
99 }
100 let now = config.device.now();
101
102 let bin_size = match rom_dir.get_file_size("_bin") {
103 Ok(0) => Err(Error::FileEmpty("_bin")),
104 Ok(bin_size) => Ok(bin_size),
105 Err(err) => Err(Error::OpenFile("_bin", err)),
106 }?;
107
108 let engine = {
109 let mut wasmi_config = wasmi::Config::default();
110 wasmi_config.ignore_custom_sections(true);
111 wasmi_config.consume_fuel(true);
112 if bin_size > 40 * KB {
113 wasmi_config.compilation_mode(wasmi::CompilationMode::Lazy);
114 }
115 wasmi::Engine::new(&wasmi_config)
116 };
117
118 let mut state = State::new(
119 id.clone(),
120 config.device,
121 rom_dir,
122 config.net_handler,
123 launcher,
124 );
125 state.load_app_stats()?;
126 state.load_stash()?;
127
128 let wasm_bin = {
130 let mut stream = match state.rom_dir.open_file("_bin") {
131 Ok(stream) => Ok(stream),
132 Err(err) => Err(Error::OpenFile("_bin", err)),
133 }?;
134 let bin_size = bin_size as usize;
135 let mut wasm_bin = state.device.alloc_psram(bin_size);
136 wasm_bin.resize(bin_size, 0);
137 match stream.read_exact(&mut wasm_bin) {
138 Ok(_) => {}
139 Err(embedded_io::ReadExactError::UnexpectedEof) => {
140 let err = FSError::AllocationError;
141 return Err(Error::OpenFile("_bin", err));
142 }
143 Err(embedded_io::ReadExactError::Other(err)) => {
144 let err = FSError::from(err);
145 return Err(Error::OpenFile("_bin", err));
146 }
147 }
148 wasm_bin
149 };
150
151 let mut store = wasmi::Store::new(&engine, state);
152 _ = store.set_fuel(FUEL_PER_CALL);
153 let instance = {
154 let module = wasmi::Module::new(&engine, wasm_bin)?;
155 let mut externals = Vec::new();
156 populate_externals(&mut store, &module, sudo, &mut externals)?;
157 wasmi::Instance::new(&mut store, &module, &externals)?
158 };
159
160 let runtime = Self {
161 display: config.display,
162 instance,
163 store,
164 update: None,
165 render: None,
166 before_exit: None,
167 cheat: None,
168 handle_menu: None,
169 stats: None,
170 per_frame: Duration::from_fps(u32::from(FPS)),
171 n_frames: 0,
172 lagging_frames: 0,
173 fast_frames: 0,
174 render_every: 2,
175 prev_time: now,
176 prev_lag: Duration::from_ms(0),
177 serial,
178 };
179 Ok(runtime)
180 }
181
182 pub fn set_render_every(&mut self, render_every: u8) {
186 self.render_every = render_every;
187 }
188
189 pub fn display_mut(&mut self) -> &mut D {
190 &mut self.display
191 }
192
193 pub fn run(mut self) -> Result<(), Error> {
195 self.start()?;
196 loop {
197 self.update()?;
198 }
199 }
200
201 pub fn start(&mut self) -> Result<(), Error> {
203 self.set_memory();
204
205 let ins = self.instance;
206 let f = ins.get_typed_func::<(), ()>(&self.store, "_initialize");
208 self.call_callback("_initialize", f.ok())?;
209 let f = ins.get_typed_func::<(), ()>(&self.store, "_start");
210 self.call_callback("_start", f.ok())?;
211 let f = ins.get_typed_func::<(), ()>(&self.store, "boot");
213 self.call_callback("boot", f.ok())?;
214
215 self.update = ins.get_typed_func(&self.store, "update").ok();
217 self.render = ins.get_typed_func(&self.store, "render").ok();
218 self.before_exit = ins.get_typed_func(&self.store, "before_exit").ok();
219 self.cheat = ins.get_typed_func(&self.store, "cheat").ok();
220 self.handle_menu = ins.get_typed_func(&self.store, "handle_menu").ok();
221 Ok(())
222 }
223
224 pub fn update(&mut self) -> Result<bool, Error> {
229 self.handle_serial()?;
230 let state = self.store.data_mut();
231 let menu_was_active = state.menu.active();
232 let menu_index = state.update();
233
234 if let Some(scene) = &mut state.error {
235 let res = scene.render(&mut self.display);
236 if res.is_err() {
237 return Err(Error::CannotDisplay);
238 }
239 return Ok(false);
240 }
241
242 let menu_is_active = state.menu.active();
244 if menu_is_active {
245 if self.n_frames.is_multiple_of(60) {
246 if let Some(battery) = &mut state.battery {
247 let res = battery.update(&mut state.device);
248 if let Err(err) = res {
249 state.device.log_error("battery", err);
250 }
251 }
252 }
253 let res = state.menu.render(&mut self.display, &state.battery);
258 if res.is_err() {
259 return Err(Error::CannotDisplay);
260 }
261 self.delay();
262 return Ok(false);
263 } else if menu_was_active {
264 state.frame.dirty = true;
265 if self.render.is_none() {
266 _ = self.display.clear(C::BG);
274 }
275 }
276
277 if let Some(custom_menu) = menu_index {
279 if let Some(handle_menu) = self.handle_menu {
280 if let Err(err) = handle_menu.call(&mut self.store, (custom_menu as u32,)) {
281 let stats = self.store.data().runtime_stats();
282 return Err(Error::FuncCall("handle_menu", err, stats));
283 };
284 }
285 }
286
287 let fuel_update = self.call_callback("update", self.update)?;
289 if let Some(stats) = &mut self.stats {
290 stats.update_fuel.add(fuel_update);
291 }
292 {
293 let state = self.store.data_mut();
294 let audio_buf = state.device.get_audio_buffer();
295 if !audio_buf.is_empty() {
296 state.audio.write(audio_buf);
297 }
298 }
299
300 if self.fast_frames >= FPS {
304 self.render_every = (self.render_every - 1).max(1);
305 self.fast_frames = 0;
306 } else if self.lagging_frames >= FPS {
307 self.render_every = (self.render_every + 1).min(8);
308 self.lagging_frames = 0;
309 }
310 self.delay();
311
312 let state = self.store.data();
313 let should_render = state.exit || self.n_frames.is_multiple_of(self.render_every);
314 self.n_frames = (self.n_frames + 1) % (FPS * 4);
318 if should_render {
319 let fuel_render = self.call_callback("render", self.render)?;
320 if let Some(stats) = &mut self.stats {
321 stats.render_fuel.add(fuel_render);
322 }
323 let state = self.store.data();
324 if state.frame.dirty {
325 self.flush_frame()?;
326 }
327 }
328 let state = self.store.data();
329 Ok(state.exit)
330 }
331
332 fn delay(&mut self) {
334 let state = self.store.data();
335 let now = state.device.now();
336 let elapsed = now - self.prev_time;
337 if elapsed < self.per_frame {
338 let delay = self.per_frame - elapsed;
339 if delay > self.prev_lag {
340 let delay = delay - self.prev_lag;
341 if let Some(stats) = &mut self.stats {
342 stats.delays += delay;
343 stats.lags -= self.prev_lag;
345 }
346 state.device.delay(delay);
347 }
348 self.fast_frames = (self.fast_frames + 1) % (FPS * 4);
349 self.prev_lag = Duration::from_ms(0);
350 self.lagging_frames = 0;
351 } else {
352 if let Some(stats) = &mut self.stats {
353 stats.lags += elapsed - self.per_frame;
354 }
355 self.prev_lag = elapsed - self.per_frame;
356 self.lagging_frames = (self.lagging_frames + 1) % (FPS * 4);
357 self.fast_frames = 0;
358 }
359 self.prev_time = state.device.now();
360 }
361
362 pub fn finalize(mut self) -> Result<RuntimeConfig<'a, D, C>, Error> {
369 self.call_callback("before_exit", self.before_exit)?;
370 let mut state = self.store.into_data();
371 state.save_stash();
372 state.update_app_stats();
373 state.save_app_stats();
374 let net_handler = state.net_handler.replace(NetHandler::None);
375 let config = RuntimeConfig {
376 id: state.next,
377 device: state.device,
378 display: self.display,
379 net_handler,
380 };
381 Ok(config)
382 }
383
384 pub fn device_mut(&mut self) -> &mut DeviceImpl<'a> {
385 let state = self.store.data_mut();
386 &mut state.device
387 }
388
389 fn flush_frame(&mut self) -> Result<(), Error> {
391 let state = self.store.data_mut();
392 let res = self.display.render_fb(&mut state.frame);
393 if res.is_err() {
394 return Err(Error::CannotDisplay);
395 }
396 Ok(())
397 }
398
399 fn set_memory(&mut self) {
401 let memory = self.instance.get_memory(&self.store, "memory");
402 let state = self.store.data_mut();
403 state.memory = memory;
404 }
405
406 fn handle_serial(&mut self) -> Result<(), Error> {
408 let maybe_msg = match self.serial.recv() {
409 Ok(msg) => msg,
410 Err(err) => return Err(Error::SerialRecv(err)),
411 };
412 if let Some(raw_msg) = maybe_msg {
413 match serial::Request::decode(&raw_msg) {
414 Ok(req) => self.handle_serial_request(req)?,
415 Err(err) => return Err(Error::SerialDecode(err)),
416 }
417 }
418 self.send_stats()?;
419 Ok(())
420 }
421
422 fn send_stats(&mut self) -> Result<(), Error> {
424 let Some(stats) = &mut self.stats else {
425 return Ok(());
426 };
427 let state = self.store.data();
428 let now = state.device.now();
429 if let Some(memory) = state.memory {
430 let data = memory.data(&self.store);
431 stats.analyze_memory(data);
432 }
433 let Some(resp) = stats.as_message(now) else {
434 return Ok(());
435 };
436 let encoded = match resp.encode_vec() {
437 Ok(encoded) => encoded,
438 Err(err) => return Err(Error::SerialEncode(err)),
439 };
440 let res = self.serial.send(&encoded);
441 if let Err(err) = res {
442 return Err(Error::SerialSend(err));
443 }
444 Ok(())
445 }
446
447 fn handle_serial_request(&mut self, req: serial::Request) -> Result<(), Error> {
448 match req {
449 serial::Request::Cheat(a, b) => {
450 let Some(cheat) = self.cheat else {
451 return Err(Error::CheatUndefined);
452 };
453 let state = self.store.data_mut();
454 if !matches!(state.net_handler.get_mut(), NetHandler::None) {
455 return Err(Error::CheatInNet);
456 }
457 match cheat.call(&mut self.store, (a, b)) {
458 Ok((result,)) => {
459 let resp = serial::Response::Cheat(result);
460 self.serial_send(resp)?;
461 }
462 Err(err) => {
463 let stats = self.store.data().runtime_stats();
464 return Err(Error::FuncCall("cheat", err, stats));
465 }
466 }
467 }
468 serial::Request::Stats(stats) => {
469 let state = self.store.data_mut();
470 let now = state.device.now();
471 if stats && self.stats.is_none() {
472 self.stats = Some(StatsTracker::new(now));
473 };
474 if !stats && self.stats.is_some() {
475 self.stats = None;
476 };
477 }
478 serial::Request::AppId => {
479 let state = self.store.data();
480 let author = state.id.author().into();
481 let app = state.id.app().into();
482 let resp = serial::Response::AppID((author, app));
483 self.serial_send(resp)?;
484 }
485 serial::Request::Screenshot => {
486 let state = self.store.data_mut();
487 state.take_screenshot();
488 let resp = serial::Response::Ok;
489 self.serial_send(resp)?;
490 }
491 serial::Request::Launch((author, app)) => {
492 let state = self.store.data_mut();
493 let resp = if let Some(id) = FullID::from_str(&author, &app) {
494 state.next = Some(id);
495 state.exit = true;
496 serial::Response::Ok
497 } else {
498 serial::Response::Log("ERROR(runtime): app ID is too long".into())
499 };
500 self.serial_send(resp)?;
501 }
502 serial::Request::Exit => {
503 let state = self.store.data_mut();
504 state.exit = true;
505 let resp = serial::Response::Ok;
506 self.serial_send(resp)?;
507 }
508 serial::Request::Buttons(_) => todo!(),
509 serial::Request::Data(_) => todo!(),
510 }
511 Ok(())
512 }
513
514 fn serial_send(&mut self, resp: serial::Response) -> Result<(), Error> {
515 let encoded = match resp.encode_vec() {
516 Ok(encoded) => encoded,
517 Err(err) => return Err(Error::SerialEncode(err)),
518 };
519 let res = self.serial.send(&encoded);
520 if let Err(err) = res {
521 return Err(Error::SerialSend(err));
522 }
523 Ok(())
524 }
525
526 fn call_callback(
528 &mut self,
529 name: &'static str,
530 f: Option<wasmi::TypedFunc<(), ()>>,
531 ) -> Result<u32, Error> {
532 _ = self.store.set_fuel(FUEL_PER_CALL);
533 if let Some(f) = f {
534 if let Err(err) = f.call(&mut self.store, ()) {
535 let stats = self.store.data().runtime_stats();
536 return Err(Error::FuncCall(name, err, stats));
537 }
538 }
539 let Ok(left) = self.store.get_fuel() else {
540 return Ok(0);
541 };
542 let consumed = FUEL_PER_CALL - left;
543 let consumed = u32::try_from(consumed).unwrap_or_default();
544 Ok(consumed)
545 }
546}
547
548fn detect_launcher(device: &mut DeviceImpl) -> Option<FullID> {
549 let mut dir = device.open_dir(&["sys"]).ok()?;
550 if let Some(id) = get_short_meta(&mut dir, "launcher") {
551 return Some(id);
552 }
553 get_short_meta(&mut dir, "new-app")
554}
555
556fn get_short_meta(dir: &mut DirImpl, fname: &str) -> Option<FullID> {
557 let file = dir.open_file(fname).ok()?;
558 let bytes = read_all(file).ok()?;
559 let meta = ShortMeta::decode(&bytes[..]).ok()?;
560 let author = meta.author_id.try_into().ok()?;
561 let app = meta.app_id.try_into().ok()?;
562 let id = FullID::new(author, app);
563 Some(id)
564}