diff --git a/gb-vst/src/lib.rs b/gb-vst/src/lib.rs index 1354fdc..caa2404 100644 --- a/gb-vst/src/lib.rs +++ b/gb-vst/src/lib.rs @@ -7,18 +7,16 @@ use gb_emu_lib::{ }, EmulatorCore, }; -use nih_plug::midi::MidiResult::Basic; use nih_plug::prelude::*; +use nih_plug::{midi::MidiResult::Basic, params::persist::PersistentField}; use std::sync::{ mpsc::{self, channel, Receiver, Sender}, - Arc, Mutex, + Arc, Mutex, RwLock, }; use ui::{Emulator, EmulatorRenderer}; #[cfg(feature = "savestate")] use gb_emu_lib::connect::CpuSaveState; -#[cfg(feature = "savestate")] -use nih_plug::params::persist::PersistentField; mod ui; @@ -42,11 +40,31 @@ impl PersistentField<'_, Option>> for SaveStateParam { } } +#[derive(Default)] +struct SramParam { + state: Arc>>, +} + +impl PersistentField<'_, Vec> for SramParam { + fn set(&self, new_value: Vec) { + *self.state.write().unwrap() = new_value; + } + + fn map(&self, f: F) -> R + where + F: Fn(&Vec) -> R, + { + f(&self.state.read().unwrap()) + } +} + #[derive(Params, Default)] struct EmuParams { - #[cfg(feature = "savestate")] - #[persist = "save_state"] - last_save_state: SaveStateParam, + #[persist = "sram"] + sram_save: SramParam, + // #[cfg(feature = "savestate")] + // #[persist = "save_state"] + // last_save_state: SaveStateParam, } struct EmuVars { @@ -70,7 +88,7 @@ type JoypadSender = Mutex>>; const BUFFERS_PER_FRAME: usize = 1; const DOWNSAMPLE_TYPE: DownsampleType = DownsampleType::Linear; -const ROM: &[u8; 65536] = include_bytes!("../../test-roms/mGB.gb"); +const ROM: &[u8; 65536] = include_bytes!("../../test-roms/mGB-save-patch.gb"); impl Plugin for GameboyEmu { const NAME: &'static str = "Gameboy"; @@ -262,7 +280,7 @@ impl Plugin for GameboyEmu { let mut emulator_core = { let options = EmulatorOptions::new(window, rom, output) .with_serial_target(serial_target) - .force_no_save(); + .with_sram_buffer(self.params.sram_save.state.clone()); EmulatorCore::init(receiver, options, NoCamera::default()) }; diff --git a/lib/src/connect/mod.rs b/lib/src/connect/mod.rs index 01dfdfc..086024f 100644 --- a/lib/src/connect/mod.rs +++ b/lib/src/connect/mod.rs @@ -1,5 +1,5 @@ use std::marker::PhantomData; -use std::sync::{Arc, Mutex}; +use std::sync::{Arc, Mutex, RwLock}; pub use crate::processor::memory::mmio::gpu::Colour; pub use crate::processor::memory::mmio::joypad::{JoypadButtons, JoypadState}; @@ -151,6 +151,12 @@ where } } +#[derive(Debug)] +pub enum SramType { + File(String), + RawBuffer(Arc>>), +} + #[non_exhaustive] pub struct EmulatorOptions where @@ -161,7 +167,7 @@ where pub(crate) tile_window: Option, pub(crate) rom: RomFile, pub(crate) output: AudioOutput, - pub(crate) save_path: Option, + pub(crate) save: Option, pub(crate) no_save: bool, pub(crate) bootrom: Option, @@ -182,7 +188,7 @@ where tile_window: None, rom, output, - save_path: None, + save: None, no_save: false, bootrom: None, show_bootrom: false, @@ -193,7 +199,12 @@ where } pub fn with_save_path(mut self, path: Option) -> Self { - self.save_path = path; + self.save = path.map(SramType::File); + self + } + + pub fn with_sram_buffer(mut self, buffer: Arc>>) -> Self { + self.save = Some(SramType::RawBuffer(buffer)); self } diff --git a/lib/src/lib.rs b/lib/src/lib.rs index dc1beeb..2b4fc48 100644 --- a/lib/src/lib.rs +++ b/lib/src/lib.rs @@ -6,10 +6,14 @@ use crate::{ }; use connect::{ AudioOutput, CameraWrapper, CameraWrapperRef, EmulatorCoreTrait, EmulatorMessage, - EmulatorOptions, NoCamera, PocketCamera, Renderer, RomFile, SerialTarget, + EmulatorOptions, NoCamera, PocketCamera, Renderer, RomFile, SerialTarget, SramType, }; use processor::{ - memory::{mmio::gpu::Colour, rom::CgbRomType, OutputTargets, Rom}, + memory::{ + mmio::gpu::Colour, + rom::{sram_save::SaveDataLocation, CgbRomType}, + OutputTargets, Rom, + }, Cpu, CpuSaveState, }; use std::{ @@ -54,16 +58,21 @@ where ) -> Self { let camera: CameraWrapperRef = Arc::new(Mutex::new(CameraWrapper::new(camera))); + let maybe_save = options.save.map(|i| match i { + SramType::File(sram_path) => { + SaveDataLocation::File(PathBuf::from_str(&sram_path).unwrap()) + } + SramType::RawBuffer(buffer) => SaveDataLocation::Raw(buffer), + }); + let rom = match options.rom { RomFile::Path(path) => { let maybe_save = if options.no_save { None } else { - Some(if let Some(path) = options.save_path { - PathBuf::from_str(&path).unwrap() - } else { - PathBuf::from_str(&path).unwrap().with_extension("sav") - }) + Some(maybe_save.unwrap_or(SaveDataLocation::File( + PathBuf::from_str(&path).unwrap().with_extension("sav"), + ))) }; match fs::read(path) { @@ -74,7 +83,7 @@ where } } } - RomFile::Raw(data) => Rom::load(data, None, camera.clone()), + RomFile::Raw(data) => Rom::load(data, maybe_save, camera.clone()), }; let (bootrom, cgb) = if let Some(b) = options.bootrom { diff --git a/lib/src/processor/memory/rom.rs b/lib/src/processor/memory/rom.rs index c66bf11..952899a 100644 --- a/lib/src/processor/memory/rom.rs +++ b/lib/src/processor/memory/rom.rs @@ -1,127 +1,18 @@ +use self::{ + mbcs::{ + Mbc, Mbc1, Mbc1SaveState, Mbc2, Mbc2SaveState, Mbc3, Mbc3SaveState, Mbc5, Mbc5SaveState, + None, PocketCamera, PocketCameraSaveState, + }, + sram_save::SaveDataLocation, +}; use crate::connect::{CameraWrapperRef, PocketCamera as PocketCameraTrait}; use serde::{Deserialize, Serialize}; -use std::{ - fs::{File, OpenOptions}, - io::{Read, Seek, SeekFrom, Write}, - marker::PhantomData, - path::PathBuf, - str::from_utf8, -}; - -use self::mbcs::{ - Mbc, Mbc1, Mbc1SaveState, Mbc2, Mbc2SaveState, Mbc3, Mbc3SaveState, Mbc5, Mbc5SaveState, None, - PocketCamera, PocketCameraSaveState, -}; +use std::{marker::PhantomData, str::from_utf8}; use super::addresses::{CartRamAddress, RomAddress}; mod mbcs; - -struct MaybeBufferedSram { - buf: Vec, - length: usize, - inner: Option, - unbuffered_writes: usize, -} - -#[derive(Serialize, Deserialize)] -struct SramSaveState { - buf: Vec, - length: usize, -} - -impl SramSaveState { - pub fn create(sram: &MaybeBufferedSram) -> Self { - Self { - buf: sram.buf.clone(), - length: sram.length, - } - } -} - -const NUM_WRITES_TO_FLUSH: usize = 256; - -impl MaybeBufferedSram { - fn new(path: Option, length: usize) -> Self { - let mut buf = Vec::new(); - let inner = if let Some(path) = path { - if path.exists() { - let mut writer = OpenOptions::new() - .write(true) - .read(true) - .open(path) - .unwrap(); - writer.read_to_end(&mut buf).unwrap(); - Some(writer) - } else { - buf.resize(8 * mbcs::KB, 0); - let writer = OpenOptions::new() - .write(true) - .create_new(true) - .open(path) - .unwrap(); - Some(writer) - } - } else { - None - }; - - Self { - buf, - length, - inner, - unbuffered_writes: 0, - } - } - - fn from_save_state(state: SramSaveState) -> Self { - // TODO - restore file path - Self { - buf: state.buf, - length: state.length, - inner: None, - unbuffered_writes: 0, - } - } - - fn len(&self) -> usize { - self.length - } - - fn get(&self, addr: usize) -> u8 { - if addr >= self.buf.len() { - 0 - } else { - self.buf[addr] - } - } - - fn set(&mut self, addr: usize, data: u8) { - self.unbuffered_writes += 1; - while addr >= self.buf.len() { - self.buf.resize(self.buf.len() + (8 * mbcs::KB), 0); - } - self.buf[addr] = data; - if self.unbuffered_writes >= NUM_WRITES_TO_FLUSH { - self.flush(); - } - } - - fn flush(&mut self) { - if let Some(ref mut writer) = self.inner { - writer.seek(SeekFrom::Start(0)).unwrap(); - writer.set_len(self.buf.len() as u64).unwrap(); - writer.write_all(&self.buf).unwrap(); - self.unbuffered_writes = 0; - } - } -} - -impl Drop for MaybeBufferedSram { - fn drop(&mut self) { - self.flush(); - } -} +pub mod sram_save; #[derive(Serialize, Deserialize, Debug, PartialEq)] pub(crate) enum CgbRomType { @@ -190,7 +81,7 @@ where { pub(crate) fn load( data: Vec, - save_path: Option, + sram_location: Option, camera: CameraWrapperRef, ) -> Self { let mut title_length = 0x143; @@ -213,22 +104,26 @@ where 0x00 => Box::new(None::init(data)), 0x01 => Box::new(Mbc1::init(data, rom_size, 0, None)), 0x02 => Box::new(Mbc1::init(data, rom_size, ram_size, None)), - 0x03 => Box::new(Mbc1::init(data, rom_size, ram_size, save_path)), + 0x03 => Box::new(Mbc1::init(data, rom_size, ram_size, sram_location)), 0x05 => Box::new(Mbc2::init(data, rom_size, None)), - 0x06 => Box::new(Mbc2::init(data, rom_size, save_path)), - 0x0F => Box::new(Mbc3::init(data, rom_size, 0, true, save_path)), - 0x10 => Box::new(Mbc3::init(data, rom_size, ram_size, true, save_path)), + 0x06 => Box::new(Mbc2::init(data, rom_size, sram_location)), + 0x0F => Box::new(Mbc3::init(data, rom_size, 0, true, sram_location)), + 0x10 => Box::new(Mbc3::init(data, rom_size, ram_size, true, sram_location)), 0x11 => Box::new(Mbc3::init(data, rom_size, 0, false, None)), 0x12 => Box::new(Mbc3::init(data, rom_size, ram_size, false, None)), - 0x13 => Box::new(Mbc3::init(data, rom_size, ram_size, false, save_path)), + 0x13 => Box::new(Mbc3::init(data, rom_size, ram_size, false, sram_location)), 0x19 => Box::new(Mbc5::init(data, rom_size, 0, false, None)), 0x1A => Box::new(Mbc5::init(data, rom_size, ram_size, false, None)), - 0x1B => Box::new(Mbc5::init(data, rom_size, ram_size, false, save_path)), + 0x1B => Box::new(Mbc5::init(data, rom_size, ram_size, false, sram_location)), 0x1C => Box::new(Mbc5::init(data, rom_size, 0, true, None)), 0x1D => Box::new(Mbc5::init(data, rom_size, ram_size, true, None)), - 0x1E => Box::new(Mbc5::init(data, rom_size, ram_size, true, save_path)), + 0x1E => Box::new(Mbc5::init(data, rom_size, ram_size, true, sram_location)), 0xFC => Box::new(PocketCamera::init( - data, rom_size, ram_size, save_path, camera, + data, + rom_size, + ram_size, + sram_location, + camera, )), _ => panic!("unimplemented mbc: {:#X}", data[0x147]), }; diff --git a/lib/src/processor/memory/rom/mbcs/mbc1.rs b/lib/src/processor/memory/rom/mbcs/mbc1.rs index 5dbae92..ef0b13f 100644 --- a/lib/src/processor/memory/rom/mbcs/mbc1.rs +++ b/lib/src/processor/memory/rom/mbcs/mbc1.rs @@ -1,12 +1,12 @@ -use std::path::PathBuf; - -use serde::{Deserialize, Serialize}; - use super::{ram_size_kb, rom_banks, Mbc, KB, RAM_BANK_SIZE, ROM_BANK_SIZE}; use crate::processor::memory::{ addresses::{AddressMarker, CartRamAddress, RomAddress}, - rom::{MaybeBufferedSram, MbcSaveState, SramSaveState}, + rom::{ + sram_save::{BufferedSramTrait, MaybeBufferedSram, SaveDataLocation, SramSaveState}, + MbcSaveState, + }, }; +use serde::{Deserialize, Serialize}; #[derive(Clone, Copy, Serialize, Deserialize)] enum BankingMode { @@ -37,7 +37,12 @@ pub struct Mbc1SaveState { } impl Mbc1 { - pub fn init(data: Vec, rom_size: u8, ram_size: u8, save_file: Option) -> Self { + pub fn init( + data: Vec, + rom_size: u8, + ram_size: u8, + save_file: Option, + ) -> Self { let rom_len = rom_banks(rom_size) * ROM_BANK_SIZE; // in kb let ram = ram_size_kb(ram_size).map(|s| MaybeBufferedSram::new(save_file, s * KB)); diff --git a/lib/src/processor/memory/rom/mbcs/mbc2.rs b/lib/src/processor/memory/rom/mbcs/mbc2.rs index a8f0435..086af6b 100644 --- a/lib/src/processor/memory/rom/mbcs/mbc2.rs +++ b/lib/src/processor/memory/rom/mbcs/mbc2.rs @@ -1,11 +1,11 @@ -use std::path::PathBuf; - -use serde::{Deserialize, Serialize}; - use crate::processor::memory::{ addresses::{AddressMarker, CartRamAddress, RomAddress}, - rom::{MaybeBufferedSram, MbcSaveState, SramSaveState}, + rom::{ + sram_save::{BufferedSramTrait, MaybeBufferedSram, SaveDataLocation, SramSaveState}, + MbcSaveState, + }, }; +use serde::{Deserialize, Serialize}; use super::{rom_banks, Mbc, ROM_BANK_SIZE}; @@ -26,7 +26,7 @@ pub struct Mbc2SaveState { } impl Mbc2 { - pub fn init(data: Vec, rom_size: u8, save_file: Option) -> Self { + pub fn init(data: Vec, rom_size: u8, save_file: Option) -> Self { let rom_len = rom_banks(rom_size) * ROM_BANK_SIZE; let ram = MaybeBufferedSram::new(save_file, 512); diff --git a/lib/src/processor/memory/rom/mbcs/mbc3.rs b/lib/src/processor/memory/rom/mbcs/mbc3.rs index 7a6ddb9..075cc9f 100644 --- a/lib/src/processor/memory/rom/mbcs/mbc3.rs +++ b/lib/src/processor/memory/rom/mbcs/mbc3.rs @@ -1,17 +1,16 @@ -use serde::{Deserialize, Serialize}; - use super::{ram_size_kb, rom_banks, Mbc, KB, RAM_BANK_SIZE, ROM_BANK_SIZE}; use crate::{ processor::memory::{ addresses::{AddressMarker, CartRamAddress, RomAddress}, - rom::{MaybeBufferedSram, MbcSaveState, SramSaveState}, + rom::{ + sram_save::{BufferedSramTrait, MaybeBufferedSram, SaveDataLocation, SramSaveState}, + MbcSaveState, + }, }, util::set_or_clear_bit, }; -use std::{ - path::PathBuf, - time::{Duration, Instant}, -}; +use serde::{Deserialize, Serialize}; +use std::time::{Duration, Instant}; #[derive(Debug, Serialize, Deserialize, Clone, Copy)] enum RtcRegister { @@ -112,7 +111,7 @@ impl Mbc3 { rom_size: u8, ram_size: u8, rtc: bool, - save_file: Option, + save_file: Option, ) -> Self { let ram = ram_size_kb(ram_size).map(|s| MaybeBufferedSram::new(save_file, s * KB)); Self { diff --git a/lib/src/processor/memory/rom/mbcs/mbc5.rs b/lib/src/processor/memory/rom/mbcs/mbc5.rs index f421fbf..a1061f4 100644 --- a/lib/src/processor/memory/rom/mbcs/mbc5.rs +++ b/lib/src/processor/memory/rom/mbcs/mbc5.rs @@ -1,14 +1,14 @@ -use std::path::PathBuf; - -use serde::{Deserialize, Serialize}; - use crate::{ processor::memory::{ addresses::{AddressMarker, CartRamAddress, RomAddress}, - rom::{MaybeBufferedSram, MbcSaveState, SramSaveState}, + rom::{ + sram_save::{BufferedSramTrait, MaybeBufferedSram, SaveDataLocation, SramSaveState}, + MbcSaveState, + }, }, util::get_bit, }; +use serde::{Deserialize, Serialize}; use super::{ram_size_kb, rom_banks, Mbc, KB, RAM_BANK_SIZE, ROM_BANK_SIZE}; @@ -42,7 +42,7 @@ impl Mbc5 { rom_size: u8, ram_size: u8, rumble: bool, - save_file: Option, + save_file: Option, ) -> Self { let ram = ram_size_kb(ram_size).map(|s| MaybeBufferedSram::new(save_file, s * KB)); Self { diff --git a/lib/src/processor/memory/rom/mbcs/pocketcamera.rs b/lib/src/processor/memory/rom/mbcs/pocketcamera.rs index 3299b19..87ef44d 100644 --- a/lib/src/processor/memory/rom/mbcs/pocketcamera.rs +++ b/lib/src/processor/memory/rom/mbcs/pocketcamera.rs @@ -3,11 +3,13 @@ use crate::{ connect::{CameraWrapperRef, PocketCamera as PocketCameraTrait}, processor::memory::{ addresses::{AddressMarker, CartRamAddress, RomAddress}, - rom::{MaybeBufferedSram, MbcSaveState}, + rom::{ + sram_save::{BufferedSramTrait, MaybeBufferedSram, SaveDataLocation}, + MbcSaveState, + }, }, }; use serde::{Deserialize, Serialize}; -use std::path::PathBuf; enum RamBank { Ram(u8), @@ -41,7 +43,7 @@ where data: Vec, rom_size: u8, ram_size: u8, - save_file: Option, + save_file: Option, camera: CameraWrapperRef, ) -> Self { let ram = ram_size_kb(ram_size).map(|s| MaybeBufferedSram::new(save_file, s * KB)); diff --git a/lib/src/processor/memory/rom/sram_save.rs b/lib/src/processor/memory/rom/sram_save.rs new file mode 100644 index 0000000..d804c1d --- /dev/null +++ b/lib/src/processor/memory/rom/sram_save.rs @@ -0,0 +1,246 @@ +use serde::{Deserialize, Serialize}; +use std::{ + fs::{File, OpenOptions}, + io::{Read, Seek, SeekFrom, Write}, + path::PathBuf, + sync::{Arc, RwLock}, +}; + +use super::mbcs; + +#[derive(Debug)] +pub enum SaveDataLocation { + File(PathBuf), + Raw(Arc>>), +} + +pub(crate) enum MaybeBufferedSram { + File(FileBufferedSram), + Raw(RawBufferedSram), + Unbuffered(UnbufferedSram), +} + +impl MaybeBufferedSram { + pub(crate) fn new(save: Option, length: usize) -> Self { + match save { + Some(SaveDataLocation::File(path)) => Self::File(FileBufferedSram::new(path, length)), + Some(SaveDataLocation::Raw(buf)) => Self::Raw(RawBufferedSram::new(buf, length)), + None => Self::Unbuffered(UnbufferedSram::new(length)), + } + } + + pub(crate) fn from_save_state(state: SramSaveState) -> Self { + Self::Unbuffered(UnbufferedSram::new(state.length)) + } +} + +pub(crate) struct UnbufferedSram { + buf: Vec, +} + +impl UnbufferedSram { + fn new(length: usize) -> Self { + let mut buf = Vec::new(); + buf.resize(length, 0); + Self { buf } + } +} + +impl BufferedSramTrait for UnbufferedSram { + fn len(&self) -> usize { + self.buf.len() + } + + fn get(&self, addr: usize) -> u8 { + if addr >= self.buf.len() { + 0 + } else { + self.buf[addr] + } + } + + fn set(&mut self, addr: usize, data: u8) { + if addr < self.buf.len() { + self.buf[addr] = data; + } + } + + fn flush(&mut self) {} +} + +pub(crate) struct RawBufferedSram { + buf: Arc>>, +} + +impl RawBufferedSram { + fn new(buf: Arc>>, length: usize) -> Self { + if let Ok(mut buf) = buf.write() { + buf.resize(length, 0); + } + Self { buf } + } +} + +impl BufferedSramTrait for RawBufferedSram { + fn len(&self) -> usize { + match self.buf.read() { + Ok(buf) => buf.len(), + Err(e) => panic!("failed to lock sram buffer: {e}"), + } + } + + fn get(&self, addr: usize) -> u8 { + match self.buf.read() { + Ok(buf) => { + if addr >= buf.len() { + 0 + } else { + buf[addr] + } + } + Err(e) => panic!("failed to lock sram buffer: {e}"), + } + } + + fn set(&mut self, addr: usize, data: u8) { + match self.buf.write() { + Ok(mut buf) => { + if addr < buf.len() { + buf[addr] = data; + } + } + Err(e) => panic!("failed to lock sram buffer: {e}"), + } + } + + fn flush(&mut self) {} +} + +pub(crate) struct FileBufferedSram { + buf: Vec, + length: usize, + inner: File, + unbuffered_writes: usize, +} + +const NUM_WRITES_TO_FLUSH: usize = 256; + +impl FileBufferedSram { + fn new(path: PathBuf, length: usize) -> Self { + let mut buf = Vec::new(); + let inner = { + if path.exists() { + let mut writer = OpenOptions::new() + .write(true) + .read(true) + .open(path) + .unwrap(); + writer.read_to_end(&mut buf).unwrap(); + writer + } else { + buf.resize(8 * mbcs::KB, 0); + let writer = OpenOptions::new() + .write(true) + .create_new(true) + .open(path) + .unwrap(); + writer + } + }; + + Self { + buf, + length, + inner, + unbuffered_writes: 0, + } + } +} + +impl BufferedSramTrait for FileBufferedSram { + fn len(&self) -> usize { + self.length + } + + fn get(&self, addr: usize) -> u8 { + if addr >= self.buf.len() { + 0 + } else { + self.buf[addr] + } + } + + fn set(&mut self, addr: usize, data: u8) { + self.unbuffered_writes += 1; + while addr >= self.buf.len() { + self.buf.resize(self.buf.len() + (8 * mbcs::KB), 0); + } + self.buf[addr] = data; + if self.unbuffered_writes >= NUM_WRITES_TO_FLUSH { + self.flush(); + } + } + + fn flush(&mut self) { + self.inner.seek(SeekFrom::Start(0)).unwrap(); + self.inner.set_len(self.buf.len() as u64).unwrap(); + self.inner.write_all(&self.buf).unwrap(); + self.unbuffered_writes = 0; + } +} + +pub(crate) trait BufferedSramTrait { + fn len(&self) -> usize; + fn get(&self, addr: usize) -> u8; + fn set(&mut self, addr: usize, data: u8); + fn flush(&mut self); +} + +impl BufferedSramTrait for MaybeBufferedSram { + fn len(&self) -> usize { + match self { + MaybeBufferedSram::File(f) => f.len(), + MaybeBufferedSram::Raw(r) => r.len(), + MaybeBufferedSram::Unbuffered(u) => u.len(), + } + } + + fn get(&self, addr: usize) -> u8 { + match self { + MaybeBufferedSram::File(f) => f.get(addr), + MaybeBufferedSram::Raw(r) => r.get(addr), + MaybeBufferedSram::Unbuffered(u) => u.get(addr), + } + } + + fn set(&mut self, addr: usize, data: u8) { + match self { + MaybeBufferedSram::File(f) => f.set(addr, data), + MaybeBufferedSram::Raw(r) => r.set(addr, data), + MaybeBufferedSram::Unbuffered(u) => u.set(addr, data), + } + } + + fn flush(&mut self) { + if let Self::File(ref mut f) = self { + f.flush(); + } + } +} + +#[derive(Serialize, Deserialize)] +pub(crate) struct SramSaveState { + length: usize, +} + +impl SramSaveState { + pub fn create(sram: &MaybeBufferedSram) -> Self { + Self { length: sram.len() } + } +} + +impl Drop for MaybeBufferedSram { + fn drop(&mut self) { + self.flush(); + } +}