diff --git a/Cargo.lock b/Cargo.lock index ec9a61f1..93649695 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -395,6 +395,12 @@ dependencies = [ "rustc-demangle", ] +[[package]] +name = "base64" +version = "0.21.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d297deb1925b89f2ccc13d7635fa0714f12c87adce1c75356b39ca9b7178567" + [[package]] name = "base64" version = "0.22.1" @@ -1132,6 +1138,7 @@ dependencies = [ "epaint", "log", "nohash-hasher", + "ron", "serde", ] @@ -3113,7 +3120,7 @@ version = "0.12.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "566cafdd92868e0939d3fb961bd0dc25fcfaaed179291093b3d43e6b3150ea10" dependencies = [ - "base64", + "base64 0.22.1", "bytes", "encoding_rs", "futures-channel", @@ -3182,6 +3189,18 @@ dependencies = [ "crossbeam-utils", ] +[[package]] +name = "ron" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b91f7eff05f748767f183df4320a63d6936e9c6107d97c9e6bdd9784f4289c94" +dependencies = [ + "base64 0.21.7", + "bitflags 2.5.0", + "serde", + "serde_derive", +] + [[package]] name = "rustc-demangle" version = "0.1.24" @@ -3213,7 +3232,7 @@ version = "2.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "29993a25686778eb88d4189742cd713c9bce943bc54251a33509dc63cbacf73d" dependencies = [ - "base64", + "base64 0.22.1", "rustls-pki-types", ] diff --git a/tetanes/Cargo.toml b/tetanes/Cargo.toml index bc280076..e72a96d4 100644 --- a/tetanes/Cargo.toml +++ b/tetanes/Cargo.toml @@ -51,7 +51,11 @@ crossbeam = "0.8" # TODO: Remove once https://github.com/emilk/egui/pull/4372 is released color-hex = "0.2" dirs.workspace = true -egui = { version = "0.27", features = ["extra_debug_asserts", "log"] } +egui = { version = "0.27", features = [ + "extra_debug_asserts", + "log", + "persistence", +] } egui-wgpu = { version = "0.27", features = ["winit", "wayland", "x11"] } egui_extras = { version = "0.27", default-features = false, features = [ "image", diff --git a/tetanes/src/nes/event.rs b/tetanes/src/nes/event.rs index 3cc1b3b2..8f3aba0a 100644 --- a/tetanes/src/nes/event.rs +++ b/tetanes/src/nes/event.rs @@ -483,6 +483,9 @@ impl Running { #[cfg(feature = "profiling")] puffin::set_scopes_on(false); + if let Err(err) = self.renderer.save() { + error!("failed to save rendererer state: {err:?}"); + } self.renderer.destroy(); if let Err(err) = self.cfg.save() { diff --git a/tetanes/src/nes/renderer.rs b/tetanes/src/nes/renderer.rs index 1f9d8afb..5b30f21c 100644 --- a/tetanes/src/nes/renderer.rs +++ b/tetanes/src/nes/renderer.rs @@ -11,6 +11,7 @@ use crate::{ platform::{self, BuilderExt}, thread, }; +use anyhow::Context; use egui::{ ahash::HashMap, DeferredViewportUiCallback, ImmediateViewport, SystemTheme, Vec2, ViewportBuilder, ViewportClass, ViewportCommand, ViewportId, ViewportIdMap, ViewportIdPair, @@ -21,6 +22,7 @@ use egui_winit::EventResponse; use parking_lot::Mutex; use std::{cell::RefCell, collections::hash_map::Entry, rc::Rc, sync::Arc}; use tetanes_core::{ + fs, ppu::Ppu, time::{Duration, Instant}, video::Frame, @@ -29,7 +31,7 @@ use thingbuf::{ mpsc::{blocking::Receiver as BufReceiver, errors::TryRecvError}, Recycle, }; -use tracing::{debug, error, warn}; +use tracing::{debug, error, info, warn}; use winit::{ event::WindowEvent, event_loop::{EventLoopProxy, EventLoopWindowTarget}, @@ -257,6 +259,10 @@ impl Renderer { }); } + if let Err(err) = Self::load(&ctx) { + tracing::error!("{err:?}"); + } + Ok(Self { state, frame_rx, @@ -632,6 +638,31 @@ impl Renderer { self.gui.error = Some(err.to_string()); } + pub fn load(ctx: &egui::Context) -> anyhow::Result<()> { + let path = Config::default_config_dir().join("gui.dat"); + if fs::exists(&path) { + let data = fs::load_raw(path).context("failed to load gui memory")?; + let memory = bincode::deserialize(&data).context("failed to deserialize gui memory")?; + ctx.memory_mut(|mem| { + *mem = memory; + }); + info!("Loaded UI state"); + } + Ok(()) + } + + pub fn save(&self) -> anyhow::Result<()> { + let path = Config::default_config_dir().join("gui.dat"); + self.ctx.memory(|mem| { + let data = bincode::serialize(&mem).context("failed to serialize gui memory")?; + fs::save_raw(path, &data).context("failed to save gui memory") + })?; + + info!("Saved UI state"); + + Ok(()) + } + pub fn create_window( event_loop: &EventLoopWindowTarget, ctx: &egui::Context,