rustzx_core/emulator/
mod.rs

1//! Platform-independent high-level Emulator interaction module
2mod 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/// Represents emulator stop reason
38#[derive(Clone, Copy, PartialEq, Eq)]
39pub enum EmulationStopReason {
40    /// Requested frames count have been emulated successfully
41    Completed,
42    /// Emulation time limit has been reached
43    Timeout,
44    /// Emulator has reached breakpoint address
45    Breakpoint,
46}
47
48/// Represents emulator emulation result
49pub struct EmulationInfo {
50    /// Emulation duration in emulated time (not real time)
51    pub duration: Duration,
52    /// Emulation stop reason, see [EmulationStopReason]
53    pub stop_reason: EmulationStopReason,
54}
55
56/// Represents main Emulator structure
57pub 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    /// Constructs new emulator
69    /// # Arguments
70    /// `settings` - emulator settings
71    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    /// changes emulation speed
94    pub fn set_speed(&mut self, new_speed: EmulationMode) {
95        self.mode = new_speed;
96    }
97
98    /// changes fast loading flag
99    pub fn set_fast_load(&mut self, value: bool) {
100        self.fast_load = value;
101    }
102
103    /// changes sound playback flag
104    #[cfg(feature = "sound")]
105    pub fn set_sound(&mut self, value: bool) {
106        self.sound_enabled = value;
107    }
108
109    /// function for sound generation request check
110    #[cfg(feature = "sound")]
111    pub fn have_sound(&self) -> bool {
112        // enable sound only if speed is normal
113        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    /// Rewinds tape. May return error if underlying tape asset failed to
190    /// perform seek operation to go back to the the beginning of the tape
191    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    /// Sets [Host::DebugInterface] for the emulator instance
213    pub fn set_debug_interface(&mut self, debug_interface: H::DebugInterface) {
214        self.controller.debug_interface = Some(debug_interface);
215    }
216
217    /// Returns current [Host::DebugInterface] instance
218    pub fn debug_interface(&mut self) -> Option<&mut H::DebugInterface> {
219        self.controller.debug_interface.as_mut()
220    }
221
222    /// Reads byte from memory
223    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    /// Execute `poke::Poke` action on the emulator
274    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    /// Perform emulatio up to `emulation_limit` duration, returns actual elapsed duration
285    pub fn emulate_frames(&mut self, emulation_limit: Duration) -> Result<EmulationInfo> {
286        let stopwatch = H::EmulationStopwatch::new();
287        // frame loop
288        loop {
289            // reset controller internal frame counter
290            self.controller.reset_frame_counter();
291            'cpu: loop {
292                // Emulation step. if instant event happened then accept in and execute
293                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 time is bigger than `max_time` then stop emulation cycle
328            if stopwatch.measure() > emulation_limit {
329                return Ok(EmulationInfo {
330                    duration: stopwatch.measure(),
331                    stop_reason: EmulationStopReason::Timeout,
332                });
333            }
334        }
335    }
336}