1use crate::battery::Battery;
2use crate::canvas::Canvas;
3use crate::color::Rgb16;
4use crate::config::FullID;
5use crate::error::RuntimeStats;
6use crate::error_scene::ErrorScene;
7use crate::frame_buffer::FrameBuffer;
8use crate::menu::{Menu, MenuItem};
9use crate::net::*;
10use crate::utils::{read_all, read_all_into};
11use crate::Error;
12use alloc::borrow::ToOwned;
13use alloc::boxed::Box;
14use core::cell::Cell;
15use core::fmt::Display;
16use core::str::FromStr;
17use embedded_graphics::pixelcolor::{Rgb888, RgbColor};
18use embedded_io::Write;
19use firefly_hal::*;
20use firefly_types::Encode;
21
22#[allow(private_interfaces)]
23pub enum NetHandler {
24 None,
25 Connector(Box<Connector>),
26 Connection(Box<Connection>),
27 FrameSyncer(Box<FrameSyncer>),
28}
29
30pub(crate) struct State<'a> {
31 pub device: DeviceImpl<'a>,
33
34 pub rom_dir: DirImpl,
35
36 pub menu: Menu,
38
39 launcher: bool,
40
41 pub error: Option<ErrorScene>,
42
43 pub audio: firefly_audio::Manager,
45
46 pub id: FullID,
48
49 pub frame: FrameBuffer,
51
52 pub canvas: Option<Canvas>,
54
55 pub seed: u32,
57
58 pub lock_seed: bool,
61
62 pub memory: Option<wasmi::Memory>,
66
67 pub exit: bool,
69
70 pub next: Option<FullID>,
72
73 pub input: Option<InputState>,
75
76 pub called: &'static str,
78
79 pub settings: firefly_types::Settings,
81
82 pub battery: Option<Battery>,
84
85 pub app_stats: Option<firefly_types::Stats>,
86 n_frames: u32,
88 pub stash: alloc::vec::Vec<u8>,
89 pub stash_dirty: bool,
90
91 pub net_handler: Cell<NetHandler>,
92 action: Action,
93}
94
95impl<'a> State<'a> {
96 pub(crate) fn new(
101 id: FullID,
102 device: DeviceImpl<'a>,
103 rom_dir: DirImpl,
104 net_handler: NetHandler,
105 launcher: bool,
106 ) -> Box<Self> {
107 let seed = match &net_handler {
108 NetHandler::FrameSyncer(syncer) => syncer.shared_seed,
109 _ => 0,
110 };
111 let mut device = device;
112 let maybe_battery = Battery::new(&mut device);
113 let settings = load_settings(&mut device).unwrap_or_default();
114 Box::new(Self {
115 device,
116 rom_dir,
117 id,
118 frame: FrameBuffer::new(),
119 canvas: None,
120 menu: Menu::new(),
121 launcher,
122 error: None,
123 audio: firefly_audio::Manager::new(),
124 battery: maybe_battery.ok(),
125 seed,
126 lock_seed: false,
127 memory: None,
128 next: None,
129 exit: false,
130 input: None,
131 called: "",
132 net_handler: Cell::new(net_handler),
133 settings,
134 app_stats: None,
135 n_frames: 0,
136 stash: alloc::vec::Vec::new(),
137 stash_dirty: false,
138 action: Action::None,
139 })
140 }
141
142 pub(crate) fn load_app_stats(&mut self) -> Result<(), Error> {
144 let dir_path = &["data", self.id.author(), self.id.app()];
145 let mut dir = match self.device.open_dir(dir_path) {
148 Ok(dir) => dir,
149 Err(err) => return Err(Error::OpenDir(dir_path.join("/"), err)),
150 };
151
152 let stream = match dir.open_file("stats") {
153 Ok(file) => file,
154 Err(err) => return Err(Error::OpenFile("stats", err)),
155 };
156 let raw = match read_all(stream) {
157 Ok(raw) => raw,
158 Err(err) => return Err(Error::ReadFile("stats", err.into())),
159 };
160 let stats = match firefly_types::Stats::decode(&raw) {
161 Ok(stats) => stats,
162 Err(err) => return Err(Error::DecodeStats(err)),
163 };
164 self.app_stats = Some(stats);
165 Ok(())
166 }
167
168 pub(crate) fn load_stash(&mut self) -> Result<(), Error> {
170 let dir_path = &["data", self.id.author(), self.id.app()];
171 let mut dir = match self.device.open_dir(dir_path) {
172 Ok(dir) => dir,
173 Err(err) => return Err(Error::OpenDir(dir_path.join("/"), err)),
174 };
175
176 let stream = match dir.open_file("stash") {
177 Ok(file) => file,
178 Err(FSError::NotFound) => return Ok(()),
179 Err(err) => return Err(Error::OpenFile("stash", err)),
180 };
181 let res = read_all_into(stream, &mut self.stash);
182 if let Err(err) = res {
183 return Err(Error::ReadFile("stash", err.into()));
184 };
185 Ok(())
186 }
187
188 pub(crate) fn runtime_stats(&self) -> RuntimeStats {
189 RuntimeStats {
190 last_called: self.called,
191 }
192 }
193
194 pub(crate) fn set_next(&mut self, app: Option<FullID>) {
196 match self.net_handler.get_mut() {
197 NetHandler::None | NetHandler::Connector(_) => {
198 self.next = app;
199 self.exit = true;
200 }
201 NetHandler::FrameSyncer(_) => {
202 let action = match app {
203 Some(id) if id == self.id => Action::Restart,
204 Some(_) => panic!("cannot launch another app in multiplayer"),
205 None => Action::Exit,
206 };
207 self.action = action;
208 }
209 NetHandler::Connection(c) => {
210 let Some(app) = app else { return };
211 let res = c.set_app(&mut self.device, app);
212 if let Err(err) = res {
213 self.device.log_error("netcode", err);
214 }
215 }
216 };
217 }
218
219 pub(crate) fn save_stash(&mut self) {
221 if !self.stash_dirty {
222 return;
223 }
224 let dir_path = &["data", self.id.author(), self.id.app()];
225 let mut dir = match self.device.open_dir(dir_path) {
226 Ok(dir) => dir,
227 Err(err) => {
228 self.device.log_error("stash", err);
229 return;
230 }
231 };
232
233 if self.stash.is_empty() {
235 let res = dir.remove_file("stash");
236 if let Err(err) = res {
237 self.device.log_error("stash", err);
238 }
239 return;
240 };
241
242 let mut stream = match dir.create_file("stash") {
243 Ok(stream) => stream,
244 Err(err) => {
245 self.device.log_error("stash", err);
246 return;
247 }
248 };
249 let res = stream.write_all(&self.stash[..]);
250 if let Err(err) = res {
251 let err = FSError::from(err);
252 self.device.log_error("stash", err);
253 }
254 }
255
256 pub(crate) fn update_app_stats(&mut self) {
260 let players = self.player_count().min(4);
261 let idx = players - 1;
262 let Some(stats) = self.app_stats.as_mut() else {
263 return;
264 };
265 stats.launches[idx] += 1;
266 let minutes = self.n_frames / 3600;
267 stats.minutes[idx] += minutes;
268 if minutes > stats.longest_play[idx] {
269 stats.longest_play[idx] = minutes;
270 }
271 }
272
273 fn player_count(&mut self) -> usize {
275 match self.net_handler.get_mut() {
276 NetHandler::None => 1,
277 NetHandler::Connector(connector) => connector.peer_infos().len() + 1,
278 NetHandler::Connection(connection) => connection.peers.len(),
279 NetHandler::FrameSyncer(frame_syncer) => frame_syncer.peers.len(),
280 }
281 }
282
283 pub(crate) fn save_app_stats(&mut self) {
285 let Some(stats) = &self.app_stats else {
286 return;
287 };
288 let res = match stats.encode_vec() {
289 Ok(res) => res,
290 Err(err) => {
291 self.device.log_error("stats", err);
292 return;
293 }
294 };
295 let dir_path = &["data", self.id.author(), self.id.app()];
296 let mut dir = match self.device.open_dir(dir_path) {
297 Ok(dir) => dir,
298 Err(err) => {
299 self.device.log_error("stats", err);
300 return;
301 }
302 };
303 let mut stream = match dir.create_file("stats") {
304 Ok(stream) => stream,
305 Err(err) => {
306 self.device.log_error("stats", err);
307 return;
308 }
309 };
310 let res = stream.write_all(&res);
311 if let Err(err) = res {
312 let err = FSError::from(err);
313 self.device.log_error("stats", err);
314 }
315 }
316
317 pub(crate) fn update(&mut self) -> Option<u8> {
319 self.n_frames += 1;
320 if let Some(scene) = self.error.as_mut() {
321 let close = scene.update(&mut self.device);
322 if close {
323 self.error = None;
324 }
325 }
326
327 if self.error.is_none() {
328 self.input = self.device.read_input();
329 }
330 self.update_net();
331
332 let input = match self.net_handler.get_mut() {
339 NetHandler::None => self.input.clone(),
341 NetHandler::Connector(_) => return None,
343 NetHandler::Connection(_) => self.input.clone(),
345 NetHandler::FrameSyncer(syncer) => {
347 match &self.input {
350 Some(input) => {
351 let mut input = input.clone();
355 if syncer.get_combined_input().menu() {
356 input.buttons |= 0b10000;
357 } else {
358 input.buttons &= !0b10000;
359 };
360 Some(input)
361 }
362 None => {
363 if syncer.get_combined_input().menu() {
364 Some(InputState {
365 pad: None,
366 buttons: 0b10000,
367 })
368 } else {
369 None
370 }
371 }
372 }
373 }
374 };
375
376 if !self.launcher {
377 let action = self.menu.handle_input(&input);
378 if let Some(action) = action {
379 match action {
380 MenuItem::Custom(index, _) => return Some(*index),
381 MenuItem::ScreenShot => self.take_screenshot(),
382 MenuItem::Restart => self.set_next(Some(self.id.clone())),
383 MenuItem::Quit => self.set_next(None),
384 };
385 };
386 }
387 None
388 }
389
390 fn update_net(&mut self) {
391 let handler = self.net_handler.replace(NetHandler::None);
392 let handler = match handler {
393 NetHandler::Connector(connector) => self.update_connector(connector),
394 NetHandler::None => NetHandler::None,
395 NetHandler::Connection(connection) => self.update_connection(connection),
396 NetHandler::FrameSyncer(syncer) => self.update_syncer(syncer),
397 };
398 self.net_handler.replace(handler);
399 }
400
401 fn update_connector(&mut self, mut connector: Box<Connector>) -> NetHandler {
402 let res = connector.update(&mut self.device);
403 if let Err(err) = res {
404 self.error = Some(ErrorScene::new(alloc::format!("{err}")));
405 self.device.log_error("netcode", err);
406 return NetHandler::Connector(connector);
407 }
408 let Some(mut conn_status) = connector.status else {
409 return NetHandler::Connector(connector);
410 };
411 if conn_status == ConnectStatus::Finished && connector.peer_infos().is_empty() {
414 conn_status = ConnectStatus::Cancelled;
415 }
416 match conn_status {
417 ConnectStatus::Stopped => {
418 let res = connector.pause();
419 if let Err(err) = res {
420 self.device.log_error("netcode", err);
421 }
422 NetHandler::Connector(connector)
423 }
424 ConnectStatus::Cancelled => {
425 self.set_next(None);
426 let res = connector.cancel(&mut self.device);
427 if let Err(err) = res {
428 self.device.log_error("netcode", err);
429 }
430 NetHandler::None
431 }
432 ConnectStatus::Finished => {
433 if let Err(err) = connector.validate() {
434 self.error = Some(ErrorScene::new(err.to_owned()))
435 }
436 self.set_next(None);
437 let connection = connector.finalize(&mut self.device);
438 NetHandler::Connection(connection)
439 }
440 }
441 }
442
443 fn update_connection(&mut self, mut connection: Box<Connection>) -> NetHandler {
444 let status = connection.update(&mut self.device);
445 match status {
446 ConnectionStatus::Launching => {
447 if let Some(app_id) = &connection.app {
448 self.set_next(Some(app_id.clone()));
449 let syncer = connection.finalize(&mut self.device);
450 return NetHandler::FrameSyncer(syncer);
451 }
452 }
453 ConnectionStatus::Timeout => {
454 let msg = "timed out waiting for other devices to launch the app";
455 self.device.log_error("netcode", msg);
456 self.set_next(None);
457 return NetHandler::None;
458 }
459 _ => (),
460 }
461 NetHandler::Connection(connection)
462 }
463
464 fn update_syncer(&mut self, mut syncer: Box<FrameSyncer>) -> NetHandler {
465 let sync_rand = !self.lock_seed && self.seed != 0 && syncer.frame % 60 == 21;
469 let rand = if sync_rand { self.device.random() } else { 0 };
470
471 let input = self.input.clone().unwrap_or_default();
472 let frame_state = FrameState {
473 frame: 0,
476 rand,
477 input: Input {
478 pad: input.pad.map(Into::into),
479 buttons: input.buttons,
480 },
481 action: self.action,
482 };
483
484 syncer.advance(&mut self.device, frame_state);
485 while !syncer.ready() {
486 let res = syncer.update(&mut self.device);
487 if let Err(err) = res {
488 self.device.log_error("netcode", err);
489 self.set_next(None);
490 return NetHandler::None;
491 }
492 }
493
494 let action = syncer.get_action();
495 match action {
496 Action::None => (),
497 Action::Restart => {
498 self.next = Some(self.id.clone());
499 self.exit = true;
500 self.menu.deactivate();
501 }
502 Action::Exit => {
503 self.exit = true;
504 self.menu.deactivate();
505 return NetHandler::Connection(syncer.into_connection());
506 }
507 }
508
509 if sync_rand {
510 let seed = syncer.get_seed();
511 if seed != 0 {
512 self.seed = seed;
513 }
514 }
515 NetHandler::FrameSyncer(syncer)
516 }
517
518 pub fn take_screenshot(&mut self) {
520 let dir_path = &["data", self.id.author(), self.id.app(), "shots"];
521 let mut dir = match self.device.open_dir(dir_path) {
522 Ok(dir) => dir,
523 Err(err) => {
524 self.device.log_error("shot", err);
525 return;
526 }
527 };
528
529 let mut index = 1;
530 _ = dir.iter_dir(|_, _| index += 1);
531 let file_name = alloc::format!("{index:03}.ffs");
532
533 let mut file = match dir.create_file(&file_name) {
534 Ok(file) => file,
535 Err(err) => {
536 self.device.log_error("shot", err);
537 return;
538 }
539 };
540 let res = write_shot(&mut file, &self.frame.palette, &*self.frame.data);
541 if let Err(err) = res {
542 let err: firefly_hal::FSError = err.into();
543 self.device.log_error("shot", err);
544 }
545 }
546
547 pub fn connect(&mut self) {
548 if !matches!(self.net_handler.get_mut(), NetHandler::None) {
549 return;
550 }
551 let name = &self.settings.name;
552 let name = heapless::String::from_str(name).unwrap_or_default();
553 let s = &self.settings;
555 let flags = u8::from(s.rotate_screen)
556 | u8::from(s.reduce_flashing) << 1
557 | u8::from(s.contrast) << 2
558 | u8::from(s.easter_eggs) << 3;
559 let me = Intro {
560 name,
561 version: 1,
562 lang: s.lang,
563 country: s.country,
564 theme: s.theme,
565 flags,
566 };
567 self.net_handler
568 .set(NetHandler::Connector(Box::new(Connector::new(me))));
569 let id = FullID::from_str("sys", "connector").unwrap();
570 self.set_next(Some(id));
571 }
572
573 pub fn disconnect(&mut self) {
574 let net_handler = self.net_handler.replace(NetHandler::None);
575 if let NetHandler::Connection(conn) = net_handler {
576 let res = conn.disconnect(&mut self.device);
577 if let Err(err) = res {
578 self.device.log_error("netcode", &err);
579 let msg = alloc::format!("{err}");
580 self.error = Some(ErrorScene::new(msg));
581 }
582 }
583 }
584
585 pub(crate) fn log_error<D: Display>(&mut self, msg: D) {
587 self.device.log_error(self.called, msg);
588 }
589}
590
591fn load_settings(device: &mut DeviceImpl) -> Option<firefly_types::Settings> {
592 let mut dir = match device.open_dir(&["sys"]) {
593 Ok(dir) => dir,
594 Err(err) => {
595 device.log_error("settings", err);
596 return None;
597 }
598 };
599 let file = match dir.open_file("config") {
600 Ok(file) => file,
601 Err(err) => {
602 device.log_error("settings", err);
603 return None;
604 }
605 };
606 let raw = match read_all(file) {
607 Ok(raw) => raw,
608 Err(err) => {
609 device.log_error("settings", FSError::from(err));
610 return None;
611 }
612 };
613 let settings = match firefly_types::Settings::decode(&raw[..]) {
614 Ok(settings) => settings,
615 Err(err) => {
616 device.log_error("settings", err);
617 return None;
618 }
619 };
620 Some(settings)
621}
622
623pub(crate) fn write_shot<W, E>(mut w: W, palette: &[Rgb16; 16], frame: &[u8]) -> Result<(), E>
625where
626 W: embedded_io::Write<Error = E>,
627{
628 w.write_all(&[0x41])?;
629 let palette = encode_palette(palette);
632 w.write_all(&palette)?;
633 w.write_all(frame)?;
634 Ok(())
635}
636
637fn encode_palette(palette: &[Rgb16; 16]) -> [u8; 16 * 3] {
639 let mut encoded: [u8; 16 * 3] = [0; 16 * 3];
640 for (i, color) in palette.iter().enumerate() {
641 let color: Rgb888 = (*color).into();
642 let i = i * 3;
643 encoded[i] = color.r();
644 encoded[i + 1] = color.g();
645 encoded[i + 2] = color.b();
646 }
647 encoded
648}