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<'a> {
24 None,
25 Connector(Box<Connector<'a>>),
26 Connection(Box<Connection<'a>>),
27 FrameSyncer(Box<FrameSyncer<'a>>),
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 settings: Option<firefly_types::Settings>,
83
84 pub battery: Option<Battery>,
86
87 pub app_stats: Option<firefly_types::Stats>,
88 pub app_stats_dirty: bool,
89 pub stash: alloc::vec::Vec<u8>,
90 pub stash_dirty: bool,
91
92 pub net_handler: Cell<NetHandler<'a>>,
93 action: Action,
94}
95
96impl<'a> State<'a> {
97 pub(crate) fn new(
102 id: FullID,
103 device: DeviceImpl<'a>,
104 rom_dir: DirImpl,
105 net_handler: NetHandler<'a>,
106 launcher: bool,
107 ) -> Box<Self> {
108 let seed = match &net_handler {
109 NetHandler::FrameSyncer(syncer) => syncer.shared_seed,
110 _ => 0,
111 };
112 let mut device = device;
113 let maybe_battery = Battery::new(&mut device);
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: None,
134 app_stats: None,
135 app_stats_dirty: false,
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 get_settings(&mut self) -> &mut firefly_types::Settings {
220 use crate::alloc::string::ToString;
221 if self.settings.is_none() {
222 let settings = self.load_settings();
223 self.settings = match settings {
224 Some(settings) => Some(settings),
225 None => Some(firefly_types::Settings {
226 xp: 0,
227 badges: 0,
228 lang: [b'e', b'n'],
229 name: "anonymous".to_string(),
230 timezone: "Europe/Amsterdam".to_string(),
231 }),
232 }
233 }
234 self.settings.as_mut().unwrap()
235 }
236
237 fn load_settings(&mut self) -> Option<firefly_types::Settings> {
238 let mut dir = match self.device.open_dir(&["sys"]) {
239 Ok(dir) => dir,
240 Err(err) => {
241 self.device.log_error("settings", err);
242 return None;
243 }
244 };
245 let file = match dir.open_file("config") {
246 Ok(file) => file,
247 Err(err) => {
248 self.device.log_error("settings", err);
249 return None;
250 }
251 };
252 let raw = match read_all(file) {
253 Ok(raw) => raw,
254 Err(err) => {
255 self.device.log_error("settings", FSError::from(err));
256 return None;
257 }
258 };
259 let settings = match firefly_types::Settings::decode(&raw[..]) {
260 Ok(settings) => settings,
261 Err(err) => {
262 self.device.log_error("settings", err);
263 return None;
264 }
265 };
266 Some(settings)
267 }
268
269 pub(crate) fn save_stash(&mut self) {
271 if !self.stash_dirty {
272 return;
273 }
274 let dir_path = &["data", self.id.author(), self.id.app()];
275 let mut dir = match self.device.open_dir(dir_path) {
276 Ok(dir) => dir,
277 Err(err) => {
278 self.device.log_error("stash", err);
279 return;
280 }
281 };
282
283 if self.stash.is_empty() {
285 let res = dir.remove_file("stash");
286 if let Err(err) = res {
287 self.device.log_error("stash", err);
288 }
289 return;
290 };
291
292 let mut stream = match dir.create_file("stash") {
293 Ok(stream) => stream,
294 Err(err) => {
295 self.device.log_error("stash", err);
296 return;
297 }
298 };
299 let res = stream.write_all(&self.stash[..]);
300 if let Err(err) = res {
301 let err = FSError::from(err);
302 self.device.log_error("stash", err);
303 }
304 }
305
306 pub(crate) fn update_app_stats(&mut self) {
310 let players = self.player_count();
311 let idx = players - 1;
312 let Some(stats) = self.app_stats.as_mut() else {
313 return;
314 };
315 self.app_stats_dirty = true;
316 stats.launches[idx] += 1;
317 }
318
319 fn player_count(&mut self) -> usize {
321 match self.net_handler.get_mut() {
322 NetHandler::None => 1,
323 NetHandler::Connector(connector) => connector.peer_infos().len() + 1,
324 NetHandler::Connection(connection) => connection.peers.len(),
325 NetHandler::FrameSyncer(frame_syncer) => frame_syncer.peers.len(),
326 }
327 }
328
329 pub(crate) fn save_app_stats(&mut self) {
331 let Some(stats) = &self.app_stats else {
332 return;
333 };
334 if !self.app_stats_dirty {
335 return;
336 }
337 let res = match stats.encode_vec() {
338 Ok(res) => res,
339 Err(err) => {
340 self.device.log_error("stats", err);
341 return;
342 }
343 };
344 let dir_path = &["data", self.id.author(), self.id.app()];
345 let mut dir = match self.device.open_dir(dir_path) {
346 Ok(dir) => dir,
347 Err(err) => {
348 self.device.log_error("stats", err);
349 return;
350 }
351 };
352 let mut stream = match dir.create_file("stats") {
353 Ok(stream) => stream,
354 Err(err) => {
355 self.device.log_error("stats", err);
356 return;
357 }
358 };
359 let res = stream.write_all(&res);
360 if let Err(err) = res {
361 let err = FSError::from(err);
362 self.device.log_error("stats", err);
363 }
364 }
365
366 pub(crate) fn update(&mut self) -> Option<u8> {
368 if let Some(scene) = self.error.as_mut() {
369 let close = scene.update(&mut self.device);
370 if close {
371 self.error = None;
372 }
373 }
374
375 if self.error.is_none() {
376 self.input = self.device.read_input();
377 }
378 self.update_net();
379
380 let input = match self.net_handler.get_mut() {
387 NetHandler::None => self.input.clone(),
389 NetHandler::Connector(_) => return None,
391 NetHandler::Connection(_) => self.input.clone(),
393 NetHandler::FrameSyncer(syncer) => {
395 match &self.input {
398 Some(input) => {
399 let mut input = input.clone();
403 if syncer.get_combined_input().menu() {
404 input.buttons |= 0b10000;
405 } else {
406 input.buttons &= !0b10000;
407 };
408 Some(input)
409 }
410 None => {
411 if syncer.get_combined_input().menu() {
412 Some(InputState {
413 pad: None,
414 buttons: 0b10000,
415 })
416 } else {
417 None
418 }
419 }
420 }
421 }
422 };
423
424 if !self.launcher {
425 let action = self.menu.handle_input(&input);
426 if let Some(action) = action {
427 match action {
428 MenuItem::Custom(index, _) => return Some(*index),
429 MenuItem::ScreenShot => self.take_screenshot(),
430 MenuItem::Restart => self.set_next(Some(self.id.clone())),
431 MenuItem::Quit => self.set_next(None),
432 };
433 };
434 }
435 None
436 }
437
438 fn update_net(&mut self) {
439 let handler = self.net_handler.replace(NetHandler::None);
440 let handler = match handler {
441 NetHandler::Connector(connector) => self.update_connector(connector),
442 NetHandler::None => NetHandler::None,
443 NetHandler::Connection(connection) => self.update_connection(connection),
444 NetHandler::FrameSyncer(syncer) => self.update_syncer(syncer),
445 };
446 self.net_handler.replace(handler);
447 }
448
449 fn update_connector<'b>(&mut self, mut connector: Box<Connector<'b>>) -> NetHandler<'b> {
450 let res = connector.update(&self.device);
451 if let Err(err) = res {
452 self.error = Some(ErrorScene::new(alloc::format!("{err}")));
453 self.device.log_error("netcode", err);
454 return NetHandler::Connector(connector);
455 }
456 let Some(mut conn_status) = connector.status else {
457 return NetHandler::Connector(connector);
458 };
459 if conn_status == ConnectStatus::Finished && connector.peer_infos().is_empty() {
462 conn_status = ConnectStatus::Cancelled;
463 }
464 match conn_status {
465 ConnectStatus::Stopped => {
466 let res = connector.pause();
467 if let Err(err) = res {
468 self.device.log_error("netcode", err);
469 }
470 NetHandler::Connector(connector)
471 }
472 ConnectStatus::Cancelled => {
473 self.set_next(None);
474 let res = connector.cancel();
475 if let Err(err) = res {
476 self.device.log_error("netcode", err);
477 }
478 NetHandler::None
479 }
480 ConnectStatus::Finished => {
481 if let Err(err) = connector.validate() {
482 self.error = Some(ErrorScene::new(err.to_owned()))
483 }
484 self.set_next(None);
485 let connection = connector.finalize();
486 NetHandler::Connection(connection)
487 }
488 }
489 }
490
491 fn update_connection<'b>(&mut self, mut connection: Box<Connection<'b>>) -> NetHandler<'b> {
492 let status = connection.update(&mut self.device);
493 match status {
494 ConnectionStatus::Launching => {
495 if let Some(app_id) = &connection.app {
496 self.set_next(Some(app_id.clone()));
497 let syncer = connection.finalize(&mut self.device);
498 return NetHandler::FrameSyncer(syncer);
499 }
500 }
501 ConnectionStatus::Timeout => {
502 let msg = "timed out waiting for other devices to launch the app";
503 self.device.log_error("netcode", msg);
504 self.set_next(None);
505 return NetHandler::None;
506 }
507 _ => (),
508 }
509 NetHandler::Connection(connection)
510 }
511
512 fn update_syncer<'b>(&mut self, mut syncer: Box<FrameSyncer<'b>>) -> NetHandler<'b> {
513 let sync_rand = !self.lock_seed && self.seed != 0 && syncer.frame % 60 == 21;
517 let rand = if sync_rand { self.device.random() } else { 0 };
518
519 let input = self.input.clone().unwrap_or_default();
520 let frame_state = FrameState {
521 frame: 0,
524 rand,
525 input: Input {
526 pad: input.pad.map(Into::into),
527 buttons: input.buttons,
528 },
529 action: self.action,
530 };
531
532 syncer.advance(&mut self.device, frame_state);
533 while !syncer.ready() {
534 let res = syncer.update(&self.device);
535 if let Err(err) = res {
536 self.device.log_error("netcode", err);
537 self.set_next(None);
538 return NetHandler::None;
539 }
540 }
541
542 let action = syncer.get_action();
543 match action {
544 Action::None => (),
545 Action::Restart => {
546 self.next = Some(self.id.clone());
547 self.exit = true;
548 self.menu.deactivate();
549 }
550 Action::Exit => {
551 self.exit = true;
552 self.menu.deactivate();
553 return NetHandler::Connection(syncer.into_connection());
554 }
555 }
556
557 if sync_rand {
558 let seed = syncer.get_seed();
559 if seed != 0 {
560 self.seed = seed;
561 }
562 }
563 NetHandler::FrameSyncer(syncer)
564 }
565
566 pub fn take_screenshot(&mut self) {
568 let dir_path = &["data", self.id.author(), self.id.app(), "shots"];
569 let mut dir = match self.device.open_dir(dir_path) {
570 Ok(dir) => dir,
571 Err(err) => {
572 self.device.log_error("shot", err);
573 return;
574 }
575 };
576
577 let mut index = 1;
578 _ = dir.iter_dir(|_, _| index += 1);
579 let file_name = alloc::format!("{index:03}.ffs");
580
581 let mut file = match dir.create_file(&file_name) {
582 Ok(file) => file,
583 Err(err) => {
584 self.device.log_error("shot", err);
585 return;
586 }
587 };
588 let res = write_shot(&mut file, &self.frame.palette, &*self.frame.data);
589 if let Err(err) = res {
590 let err: firefly_hal::FSError = err.into();
591 self.device.log_error("shot", err);
592 }
593 }
594
595 pub fn connect(&mut self) {
596 if !matches!(self.net_handler.get_mut(), NetHandler::None) {
597 return;
598 }
599 let name = &self.get_settings().name;
600 let name = heapless::String::from_str(name).unwrap_or_default();
601 let me = MyInfo { name, version: 1 };
603 let net = self.device.network();
604 self.net_handler
605 .set(NetHandler::Connector(Box::new(Connector::new(me, net))));
606 let id = FullID::from_str("sys", "connector").unwrap();
607 self.set_next(Some(id));
608 }
609
610 pub fn disconnect(&mut self) {
611 let net_handler = self.net_handler.replace(NetHandler::None);
612 if let NetHandler::Connection(conn) = net_handler {
613 let res = conn.disconnect();
614 if let Err(err) = res {
615 self.device.log_error("netcode", err);
616 }
617 }
618 }
619
620 pub(crate) fn log_error<D: Display>(&self, msg: D) {
622 self.device.log_error(self.called, msg);
623 }
624}
625
626pub(crate) fn write_shot<W, E>(mut w: W, palette: &[Rgb16; 16], frame: &[u8]) -> Result<(), E>
628where
629 W: embedded_io::Write<Error = E>,
630{
631 w.write_all(&[0x41])?;
632 let palette = encode_palette(palette);
635 w.write_all(&palette)?;
636 w.write_all(frame)?;
637 Ok(())
638}
639
640fn encode_palette(palette: &[Rgb16; 16]) -> [u8; 16 * 3] {
642 let mut encoded: [u8; 16 * 3] = [0; 16 * 3];
643 for (i, color) in palette.iter().enumerate() {
644 let color: Rgb888 = (*color).into();
645 let i = i * 3;
646 encoded[i] = color.r();
647 encoded[i + 1] = color.g();
648 encoded[i + 2] = color.b();
649 }
650 encoded
651}