1mod fastload;
3pub mod poke;
4mod screenshot;
5mod snapshot;
6
7use crate::{
8 error::RomLoadError,
9 host::{
10 DataRecorder, Host, LoadableAsset, RomFormat, RomSet, Screen, ScreenAsset, Snapshot,
11 SnapshotAsset, SnapshotRecorder, Stopwatch, Tape,
12 },
13 settings::RustzxSettings,
14 utils::EmulationMode,
15 zx::{
16 controller::ZXController,
17 events::EmulationEvents,
18 joy::{
19 kempston::KempstonKey,
20 sinclair::{SinclairJoyNum, SinclairKey},
21 },
22 keys::{CompoundKey, ZXKey},
23 mouse::kempston::{KempstonMouseButton, KempstonMouseWheelDirection},
24 tape::{Tap, TapeImpl},
25 video::colors::ZXColor,
26 },
27 Result,
28};
29use core::time::Duration;
30use rustzx_z80::Z80;
31
32#[cfg(feature = "sound")]
33use crate::zx::sound::sample::SoundSample;
34#[cfg(feature = "autoload")]
35use crate::{host::BufferCursor, zx::machine::ZXMachine};
36
37#[derive(Clone, Copy, PartialEq, Eq)]
39pub enum EmulationStopReason {
40 Completed,
42 Timeout,
44 Breakpoint,
46}
47
48pub struct EmulationInfo {
50 pub duration: Duration,
52 pub stop_reason: EmulationStopReason,
54}
55
56pub struct Emulator<H: Host> {
58 settings: RustzxSettings,
59 cpu: Z80,
60 controller: ZXController<H>,
61 mode: EmulationMode,
62 fast_load: bool,
63 #[cfg(feature = "sound")]
64 sound_enabled: bool,
65}
66
67impl<H: Host> Emulator<H> {
68 pub fn new(settings: RustzxSettings, context: H::Context) -> Result<Self> {
72 let mode = settings.emulation_mode;
73 let fast_load = settings.tape_fastload_enabled;
74 #[cfg(feature = "sound")]
75 let sound_enabled = settings.sound_enabled;
76
77 let cpu = Z80::default();
78 let controller = ZXController::<H>::new(&settings, context);
79
80 let this = Self {
81 settings,
82 cpu,
83 controller,
84 mode,
85 fast_load,
86 #[cfg(feature = "sound")]
87 sound_enabled,
88 };
89
90 Ok(this)
91 }
92
93 pub fn set_speed(&mut self, new_speed: EmulationMode) {
95 self.mode = new_speed;
96 }
97
98 pub fn set_fast_load(&mut self, value: bool) {
100 self.fast_load = value;
101 }
102
103 #[cfg(feature = "sound")]
105 pub fn set_sound(&mut self, value: bool) {
106 self.sound_enabled = value;
107 }
108
109 #[cfg(feature = "sound")]
111 pub fn have_sound(&self) -> bool {
112 if let EmulationMode::FrameCount(1) = self.mode {
114 self.sound_enabled
115 } else {
116 false
117 }
118 }
119
120 pub fn load_snapshot(&mut self, snapshot: Snapshot<impl SnapshotAsset>) -> Result<()> {
121 match snapshot {
122 Snapshot::Sna(asset) => snapshot::sna::load(self, asset),
123 }
124 }
125
126 pub fn save_snapshot<R>(&mut self, recorder: SnapshotRecorder<R>) -> Result<()>
127 where
128 R: DataRecorder,
129 {
130 match recorder {
131 SnapshotRecorder::Sna(recorder) => snapshot::sna::save(self, recorder),
132 }
133 }
134
135 pub fn load_tape(&mut self, tape: Tape<H::TapeAsset>) -> Result<()> {
136 match tape {
137 Tape::Tap(asset) => {
138 self.controller.tape = Tap::from_asset(asset)?.into();
139 }
140 }
141
142 #[cfg(feature = "autoload")]
143 if self.settings.autoload_enabled {
144 let snapshot = match self.settings.machine {
145 ZXMachine::Sinclair48K => &snapshot::autoload::tape::SNAPSHOT_SNA_48K,
146 ZXMachine::Sinclair128K => &snapshot::autoload::tape::SNAPSHOT_SNA_128K,
147 };
148
149 self.load_snapshot(Snapshot::Sna(BufferCursor::new(snapshot)))?;
150 }
151
152 Ok(())
153 }
154
155 fn load_rom_binary_16k_pages(&mut self, mut rom: impl RomSet) -> Result<()> {
156 let page_count = self.settings.machine.specs().rom_pages;
157
158 for page_index in 0..page_count {
159 let mut page_asset = rom.next_asset().ok_or(RomLoadError::MoreAssetsRequired)?;
160 let page_buffer = self.controller.memory.rom_page_data_mut(page_index);
161 page_asset.read_exact(page_buffer)?;
162 }
163
164 Ok(())
165 }
166
167 pub fn load_rom(&mut self, rom: impl RomSet) -> Result<()> {
168 match rom.format() {
169 RomFormat::Binary16KPages => self.load_rom_binary_16k_pages(rom),
170 }
171 }
172
173 pub fn load_screen(&mut self, screen: Screen<impl ScreenAsset>) -> Result<()> {
174 match screen {
175 Screen::Scr(asset) => screenshot::scr::load(self, asset)?,
176 };
177
178 Ok(())
179 }
180
181 pub fn play_tape(&mut self) {
182 self.controller.tape.play();
183 }
184
185 pub fn stop_tape(&mut self) {
186 self.controller.tape.stop();
187 }
188
189 pub fn rewind_tape(&mut self) -> Result<()> {
192 self.controller.tape.rewind()
193 }
194
195 pub fn screen_buffer(&self) -> &H::FrameBuffer {
196 self.controller.screen.frame_buffer()
197 }
198
199 #[cfg(feature = "precise-border")]
200 pub fn border_buffer(&self) -> &H::FrameBuffer {
201 self.controller.border.frame_buffer()
202 }
203
204 pub fn set_io_extender(&mut self, extender: H::IoExtender) {
205 self.controller.io_extender = Some(extender);
206 }
207
208 pub fn io_extender(&mut self) -> Option<&mut H::IoExtender> {
209 self.controller.io_extender.as_mut()
210 }
211
212 pub fn set_debug_interface(&mut self, debug_interface: H::DebugInterface) {
214 self.controller.debug_interface = Some(debug_interface);
215 }
216
217 pub fn debug_interface(&mut self) -> Option<&mut H::DebugInterface> {
219 self.controller.debug_interface.as_mut()
220 }
221
222 pub fn peek(&self, addr: u16) -> u8 {
224 self.controller.memory.read(addr)
225 }
226
227 pub fn border_color(&self) -> ZXColor {
228 self.controller.border_color
229 }
230
231 pub fn send_key(&mut self, key: ZXKey, pressed: bool) {
232 self.controller.send_key(key, pressed);
233 }
234
235 pub fn send_compound_key(&mut self, key: CompoundKey, pressed: bool) {
236 self.controller.send_compound_key(key, pressed);
237 }
238
239 pub fn send_kempston_key(&mut self, key: KempstonKey, pressed: bool) {
240 if let Some(joy) = &mut self.controller.kempston {
241 joy.key(key, pressed);
242 }
243 }
244
245 pub fn send_sinclair_key(&mut self, num: SinclairJoyNum, key: SinclairKey, pressed: bool) {
246 self.controller.send_sinclair_key(num, key, pressed);
247 }
248
249 pub fn send_mouse_button(&mut self, button: KempstonMouseButton, pressed: bool) {
250 self.controller.send_mouse_button(button, pressed);
251 }
252
253 pub fn send_mouse_wheel(&mut self, dir: KempstonMouseWheelDirection) {
254 self.controller.send_mouse_wheel(dir);
255 }
256
257 pub fn send_mouse_pos_diff(&mut self, x: i8, y: i8) {
258 self.controller.send_mouse_pos_diff(x, y);
259 }
260
261 #[cfg(feature = "sound")]
262 pub fn next_audio_sample(&mut self) -> Option<SoundSample<f32>> {
263 self.controller.mixer.pop()
264 }
265
266 fn process_fast_load_event(&mut self) -> Result<()> {
267 if self.controller.tape.can_fast_load() && self.fast_load {
268 fastload::tap::fast_load_tap(self)?;
269 }
270 Ok(())
271 }
272
273 pub fn execute_poke(&mut self, poke: impl poke::Poke) {
275 for action in poke.actions().iter().copied() {
276 match action {
277 poke::PokeAction::Mem { addr, value } => {
278 self.controller.memory.force_write(addr, value);
279 }
280 }
281 }
282 }
283
284 pub fn emulate_frames(&mut self, emulation_limit: Duration) -> Result<EmulationInfo> {
286 let stopwatch = H::EmulationStopwatch::new();
287 loop {
289 self.controller.reset_frame_counter();
291 'cpu: loop {
292 self.cpu.emulate(&mut self.controller);
294 if let Some(e) = self.controller.take_last_emulation_error() {
295 return Err(e);
296 }
297
298 let events = self.controller.take_events();
299 if !events.is_empty() {
300 if events.contains(EmulationEvents::TAPE_FAST_LOAD_TRIGGER_DETECTED) {
301 self.process_fast_load_event()?;
302 }
303 if events.contains(EmulationEvents::PC_BREAKPOINT) {
304 return Ok(EmulationInfo {
305 duration: stopwatch.measure(),
306 stop_reason: EmulationStopReason::Breakpoint,
307 });
308 }
309 }
310
311 match self.mode {
312 EmulationMode::FrameCount(frames) => {
313 if self.controller.frames_count() >= frames {
314 return Ok(EmulationInfo {
315 duration: stopwatch.measure(),
316 stop_reason: EmulationStopReason::Completed,
317 });
318 };
319 }
320 EmulationMode::Max => {
321 if self.controller.frames_count() != 0 {
322 break 'cpu;
323 }
324 }
325 }
326 }
327 if stopwatch.measure() > emulation_limit {
329 return Ok(EmulationInfo {
330 duration: stopwatch.measure(),
331 stop_reason: EmulationStopReason::Timeout,
332 });
333 }
334 }
335 }
336}