1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
mod display;
mod input;

use crossbeam::atomic::AtomicCell;
use crossbeam::sync::WaitGroup;
use std::error::Error;
use std::sync::Arc;
use std::thread;
use std::time::{Duration, Instant};
use winit::event::{Event, VirtualKeyCode};
use winit::event_loop::ControlFlow;
use winit_input_helper::WinitInputHelper;

/// The Interpreter's representation of the CHIP-8 display.
/// The display is 64x32 pixels, each pixel being either on or off, represented by a `0` or `1`, respectively.
pub type Display = [[u8; 64]; 32];

/// This type is how keyboard input is presented to the Interpreter.
/// Each of the 16 keys can either be down (`true`) or up (`false`).
pub type Keys = [bool; 16];

/// CHIP-8 interpreters can be built using this trait.
/// [`step`][Interpreter::step] should be implemented on a type representing a CHIP-8 Interpreter to run the interpreter one clock cycle at a time, such that calling it in a loop runs the interpreter.
pub trait Interpreter {
    /// Executes the next CHIP-8 Instruction, modifying the state of the virtual machine/CPU accordingly.
    /// This is the main driver function, running the interpreter one clock cycle at a time.
    /// # Return
    /// If the instruction modified the state of the display, then an updated [`Display`][Display] should be returned.
    /// # Panics
    /// Should panic if an unrecognised instruction is encountered
    fn step(&mut self, keys: &Keys) -> Option<Display>;

    /// Returns the duration of a single clock cycle, so the interpreter can keep the time steps uniform.
    /// See [`std::time`][std::time] for more information on [`Duration`][std::time::Duration].
    fn speed(&self) -> Duration;

    /// Indicates if the sound buzzer is currently active, such that the interpreter can handle sound accordingly.
    fn buzzer_active(&self) -> bool;
}

/// Starts the interpreter, blocking the current thread and running until killed.
/// Windowing, graphics, sound, and timing are all handled within this method.
pub fn run<I>(mut interpreter: I) -> !
where
    I: Interpreter + Send + 'static,
{
    let (event_loop, window, mut pixels) = display::init().expect("Could not initialise display");

    let mut input = WinitInputHelper::new();

    //include a flag so we know if the current frame has been drawn, to avoid drawing it twice
    let display = Arc::new(AtomicCell::new(([[0; 64]; 32], false)));
    let keys = Arc::new(AtomicCell::new([false; 16]));

    //used so CPU doesnt start until display is ready
    //cant start CPU after display because display has to be on the main thread and blocks it
    let wg = WaitGroup::new();

    thread::spawn({
        let wg = wg.clone();
        let display = display.clone();
        let keys = keys.clone();
        move || {
            wg.wait();
            loop {
                let t0 = Instant::now();
                //step the cpu, handle display updates
                if let Some(update) = interpreter.step(&keys.load()) {
                    display.store((update, false));
                }

                //sleep to make time steps uniform
                if let Some(sleepy_time) = interpreter.speed().checked_sub(Instant::now() - t0) {
                    thread::sleep(sleepy_time);
                }
            }
        }
    });

    wg.wait();

    event_loop.run(move |event, _, control_flow| {
        let new_frame = display.load();
        if !new_frame.1 {
            display::update(&mut pixels, &new_frame.0);
        }

        if let Event::RedrawRequested(_) = event {
            if let Err(e) = pixels.render() {
                eprintln!("Pixels rendering failure, caused by: {:?}", e.source());
                *control_flow = ControlFlow::Exit;
                return;
            }
        }

        // Handle input events
        if input.update(&event) {
            // Close events
            if input.key_pressed(VirtualKeyCode::Escape) || input.quit() {
                *control_flow = ControlFlow::Exit;
                return;
            }
            //handle keyboard input to emulator
            keys.swap(input::key_state(&input));

            // Resize the window
            if let Some(size) = input.window_resized() {
                pixels.resize_surface(size.width, size.height);
            }
        }
        window.request_redraw();
    });
}