use crate::*; pub struct DynamicEmulator { pub br: BedrockEmulator, pub debug: DebugState, } impl DynamicEmulator { pub fn new(config: &EmulatorConfig, debug: bool) -> Self { Self { br: BedrockEmulator::new(DynamicDeviceBus::new(config)), debug: DebugState::new(debug, config.symbols_path.as_ref()), } } pub fn load_program(&mut self, bytecode: &[u8]) { self.br.core.mem.load_program(bytecode); } pub fn run(&mut self) { loop { match self.br.evaluate(BATCH_SIZE, self.debug.enabled) { Some(Signal::Fork) => { self.br.core.mem.pc = 0; self.br.core.wst.sp = 0; self.br.core.rst.sp = 0; } Some(Signal::Sleep) => loop { if self.br.dev.wake() { break; } std::thread::sleep(MIN_TICK_DURATION); } Some(Signal::Halt) => { self.br.dev.stream.flush(); info!("Program halted, exiting."); self.debug.debug_summary(&self.br.core); std::process::exit(0); } Some(Signal::Debug(Debug::Debug1)) => { self.debug.debug_summary(&self.br.core); } _ => (), } if self.br.dev.input.accessed || self.br.dev.screen.accessed { return; } } } pub fn to_graphical(self, config: EmulatorConfig) -> GraphicalEmulator { GraphicalEmulator { br: BedrockEmulator { core: self.br.core, dev: GraphicalDeviceBus { system: self.br.dev.system, memory: self.br.dev.memory, math: self.br.dev.math, clock: self.br.dev.clock, input: self.br.dev.input, screen: self.br.dev.screen, stream: self.br.dev.stream, file: self.br.dev.file, } }, debug: self.debug, fullscreen: config.fullscreen, scale: config.zoom.into(), show_override_palette: config.override_palette.is_some(), render_mark: Instant::now(), config, } } pub fn to_headless(self) -> HeadlessEmulator { HeadlessEmulator { br: BedrockEmulator { core: self.br.core, dev: HeadlessDeviceBus { system: self.br.dev.system, memory: self.br.dev.memory, math: self.br.dev.math, clock: self.br.dev.clock, stream: self.br.dev.stream, file: self.br.dev.file, } }, debug: self.debug, } } } pub struct DynamicDeviceBus { pub system: SystemDevice, pub memory: MemoryDevice, pub math: MathDevice, pub clock: ClockDevice, pub input: InputDevice, pub screen: ScreenDevice, pub stream: StreamDevice, pub file: FileDevice, } impl DynamicDeviceBus { pub fn new(config: &EmulatorConfig) -> Self { Self { system: SystemDevice::new(0b1111_1100_1010_0000), memory: MemoryDevice::new(), math: MathDevice::new(), clock: ClockDevice::new(), input: InputDevice::new(), screen: ScreenDevice::new(&config), stream: StreamDevice::new(&config), file: FileDevice::new(), } } } impl DeviceBus for DynamicDeviceBus { fn read(&mut self, port: u8) -> u8 { match port & 0xf0 { 0x00 => self.system.read(port & 0x0f), 0x10 => self.memory.read(port & 0x0f), 0x20 => self.math .read(port & 0x0f), 0x30 => self.clock .read(port & 0x0f), 0x40 => self.input .read(port & 0x0f), 0x50 => self.screen.read(port & 0x0f), 0x80 => self.stream.read(port & 0x0f), 0xa0 => self.file .read(port & 0x0f), _ => 0 } } fn write(&mut self, port: u8, value: u8) -> Option { match port & 0xf0 { 0x00 => self.system.write(port & 0x0f, value), 0x10 => self.memory.write(port & 0x0f, value), 0x20 => self.math .write(port & 0x0f, value), 0x30 => self.clock .write(port & 0x0f, value), 0x40 => { self.input .write(port & 0x0f, value); Some(Signal::Interrupt) } 0x50 => { self.screen.write(port & 0x0f, value); Some(Signal::Interrupt) } 0x80 => self.stream.write(port & 0x0f, value), 0xa0 => self.file .write(port & 0x0f, value), _ => None } } fn wake(&mut self) -> bool { macro_rules! rouse { ($id:expr, $dev:ident) => { let is_eligible = test_bit!(self.system.wakers, 0x8000 >> $id); if is_eligible && self.$dev.wake() { self.system.waker = $id; self.system.asleep = false; return true; } }; } rouse!(0xa, file ); rouse!(0x8, stream); rouse!(0x5, screen); rouse!(0x4, input ); rouse!(0x3, clock ); return false; } }