ral1243/
thread.rs

1/*
2    ral1243: Emulator program as an example implementation for the z80emu library.
3    Copyright (C) 2019-2024  Rafal Michalski
4
5    For the full copyright notice, see the lib.rs file.
6*/
7//! Std thread runner for Ral1243.
8use std::io::Write;
9use std::time::{Duration, Instant};
10use std::thread::{spawn, sleep, JoinHandle};
11use std::sync::mpsc::{SyncSender, Receiver, TryRecvError};
12use log::{debug};
13use super::*;
14use super::debug::*;
15
16/// A message for controlling the emulation in a thread. `std`-only.
17#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
18pub enum RunnerMsg {
19    /// Terminates an emulation thread.
20    Terminate,
21    /// Resets the computer.
22    Reset,
23    /// Triggers the `NMI`.
24    Nmi,
25    /// Enters `DEBUG` mode or executes the next instruction.
26    DebugNext,
27    /// Enters `DEBUG` mode or runs to completion.
28    DebugCompletion,
29    /// Enters `DEBUG` mode or runs until `IRQ`.
30    DebugRunIrq,
31    /// Exits `DEBUG` mode and continues running.
32    Continue
33}
34
35impl PioStream for Receiver<u8> {
36    /// Attempt to slurp the next byte from the stream.
37    fn slurp(&mut self) -> Option<u8> {
38        self.try_recv().ok()
39    }
40}
41
42impl PioSink for SyncSender<u8> {
43    /// Attempt to flush the next byte, return whether flushing succeeds.
44    fn flush(&mut self, data: u8) -> bool {
45        self.try_send(data).is_ok()
46    }
47}
48
49
50/// Only available with `std` feature.
51impl<F: Flavour, const EXT_HZ: u32, const FRAME_HZ: u32>
52    Ral1243<F, Receiver<u8>, SyncSender<u8>, EXT_HZ, FRAME_HZ>
53{
54    /// Create a new computer and run it in a separate thread.
55    /// 
56    /// Provide `run_rx` as a [`Receiver`] of [`RunnerMsg`] to control the emulation.
57    ///
58    /// See [`Ral1243::new`] for a description of other arguments.
59    pub fn start_thread<T>(
60            run_rx: Receiver<RunnerMsg>,
61            ramsizekb: usize,
62            clock_hz: Ts,
63            exroms: Option<T>,
64            pio_stream: Receiver<u8>,
65            pio_sink: SyncSender<u8>
66        ) -> JoinHandle<()>
67        where T: IntoIterator<Item=Rom> + Send + 'static,
68              T::IntoIter: ExactSizeIterator
69    {
70        thread::spawn(move || {
71            let mut computer = Self::new(ramsizekb, clock_hz, exroms, pio_stream, pio_sink);
72            computer.run(run_rx);
73        })
74    }
75
76    fn run(&mut self, run_rx: Receiver<RunnerMsg>) {
77        let frame_duration: Duration = FrameRunner::<EXT_HZ, FRAME_HZ>::frame_duration();
78
79        let mut nmi_request = false;
80
81        let mut time = Instant::now();
82
83        let mut total_ts = 0u64;
84        let mut duration = Duration::ZERO;
85        let mut frame_count = 0;
86
87        loop {
88            if nmi_request && self.nmi().is_some() {
89                nmi_request = false;
90            }
91
92            let mtime = Instant::now();
93            let delta_ts = self.step();
94            let elapsed = mtime.elapsed();
95
96            duration += elapsed;
97            total_ts += u64::from(delta_ts);
98
99            match run_rx.try_recv() {
100                Ok(RunnerMsg::Terminate) => break,
101                Ok(RunnerMsg::Reset) => {
102                    nmi_request = false;
103                    self.reset();
104                }
105                Ok(RunnerMsg::Nmi) => {
106                    nmi_request = self.nmi().is_none();
107                }
108                Ok(RunnerMsg::DebugNext|
109                   RunnerMsg::DebugRunIrq|
110                   RunnerMsg::DebugCompletion) => {
111                    if self.debug(&run_rx, &mut nmi_request) == RunnerMsg::Terminate {
112                        break
113                    }
114                    duration = Duration::ZERO;
115                    total_ts = 0;
116                    frame_count = 0;
117                    time = Instant::now();
118                    continue
119                }
120                Ok(RunnerMsg::Continue)|
121                Err(TryRecvError::Empty) => {},
122                Err(TryRecvError::Disconnected) => break,
123            }
124
125            if let Some(duration) = frame_duration.checked_sub(time.elapsed()) {
126                thread::sleep(duration);
127            }
128            time += frame_duration;
129
130            frame_count += 1;
131            if frame_count == FRAME_HZ * 5 {
132                debug!("emulation max {:.4} MHz", total_ts as f64 / duration.as_secs_f64() / 1e6);
133                duration = Duration::ZERO;
134                total_ts = 0;
135                frame_count = 0;
136            }
137        }
138    }
139
140    /// Run to either a brkpoint just after a CALL instruction or up to the next RETx.
141    fn run_to_completion<P>(&mut self, print_debug: P) -> (Option<CpuDebug>, Ts)
142        where P: FnOnce(&CpuDebug, &Z80<F>)
143    {
144        let (dbg, ts) = self.debug_step();
145        if let Some(deb) = dbg.as_ref() {
146            if was_just_a_ret(deb, &self.cpu) {
147                return (dbg, ts);
148            }
149            else if was_just_a_call(deb, &self.cpu) {
150                print_debug(deb, &self.cpu);
151                let pc = deb.pc.wrapping_add(deb.code.len() as u16);
152                let (_, ts) = run_for(ts, FRAME_HZ, || self.run_until_brkpt(core::slice::from_ref(&pc)));
153                return (None, ts);
154            }
155        }
156        run_for(ts, FRAME_HZ, || self.debug_runto_ret())
157    }
158
159    fn debug(&mut self, run_rx: &Receiver<RunnerMsg>, nmi_request: &mut bool) -> RunnerMsg {
160        let stdout = io::stdout();
161        let mut header_lines = u8::MAX;
162        let mut last_ts = 0;
163
164        let mut print_debug = |deb: &_, cpu: &_| {
165            let mut handle = stdout.lock();
166            if header_lines > 10 {
167                let _ = writeln!(handle, "{}", Header);
168                header_lines = 0;
169            }
170            else {
171                header_lines += 1;
172            }
173            let _ = writeln!(handle, "{}", Debugger::of(deb, cpu));
174        };
175
176        loop {
177            if *nmi_request {
178                if let Some(ts) = self.nmi() {
179                    last_ts += ts;
180                    *nmi_request = false;
181                }
182            }
183            /* print preview of the potential next instruction */
184            {
185                let deb = self.debug_preview();
186                let mut handle = stdout.lock();
187                let _ = write!(handle, "{}T: +{}        \r", Preview::of(&deb), last_ts);
188                handle.flush().unwrap();
189            }
190            /* wait for orders */
191            let (deb, ts) = match run_rx.recv() {
192                Ok(RunnerMsg::Continue) => return RunnerMsg::Continue,
193                Ok(RunnerMsg::Terminate)|
194                Err(..) => return RunnerMsg::Terminate,
195                Ok(RunnerMsg::Reset) => {
196                    *nmi_request = false;
197                    self.reset();
198                    continue
199                }
200                Ok(RunnerMsg::Nmi) => {
201                    *nmi_request = true;
202                    continue
203                }
204                Ok(RunnerMsg::DebugCompletion) => self.run_to_completion(&mut print_debug),
205                Ok(RunnerMsg::DebugRunIrq) => run_for(0, FRAME_HZ, || self.debug_runto_int()),
206                Ok(RunnerMsg::DebugNext) => self.debug_step()
207            };
208
209            last_ts = ts;
210
211            if let Some(deb) = deb {
212                print_debug(&deb, &self.cpu);
213            }
214        }
215    }
216}
217
218/// Run `debug()` for given amount of frames when the closure returns (None, _)
219/// accumulating T-states starting from start_ts.
220fn run_for<D>(
221        mut start_ts: Ts,
222        mut frames: u32,
223        mut debug: impl FnMut() -> (Option<D>, Ts)
224    ) -> (Option<D>, Ts)
225{
226    loop {
227        match debug() {
228            (Some(deb), ts) => break (Some(deb), start_ts + ts),
229            (None, ts) => {
230                start_ts += ts;
231                if frames == 0 {
232                    break (None, start_ts)
233                }
234                else {
235                    frames -= 1;
236                }
237            }
238        }
239    }
240}