Skip to main content

raven_varvara/
lib.rs

1//! The Varvara computer system
2#![warn(missing_docs)]
3use log::warn;
4use std::{
5    io::Write,
6    sync::{Arc, Mutex},
7};
8
9mod console;
10mod controller;
11mod datetime;
12mod file;
13mod mouse;
14mod screen;
15mod system;
16
17/// Audio handler implementation
18mod audio;
19
20pub use audio::CHANNELS as AUDIO_CHANNELS;
21pub use audio::SAMPLE_RATE as AUDIO_SAMPLE_RATE;
22pub use audio::StreamData;
23
24pub use controller::Key;
25pub use mouse::MouseState;
26
27pub use console::spawn_worker as spawn_console_worker;
28
29use uxn::{Device, Ports, Uxn, UxnCore};
30
31/// Write to execute before calling the event vector
32#[derive(Copy, Clone, Debug)]
33struct EventData {
34    addr: u8,
35    value: u8,
36    clear: bool,
37}
38
39/// Internal events, accumulated by devices then applied to the CPU
40#[derive(Copy, Clone, Debug)]
41struct Event {
42    /// Tuple of `(address, value)` to write in in device memory
43    pub data: Option<EventData>,
44
45    /// Vector to trigger
46    pub vector: u16,
47}
48
49/// Output from [`Varvara::output`], reflecting the system's output state
50pub struct Output<'a> {
51    /// Current window size
52    pub size: (u16, u16),
53
54    /// Current screen contents, as RGBA values
55    pub frame: &'a [u8],
56
57    /// The system's mouse cursor should be hidden
58    pub hide_mouse: bool,
59
60    /// Outgoing console characters sent to the `write` port
61    pub stdout: Vec<u8>,
62
63    /// Outgoing console characters sent to the `error` port
64    pub stderr: Vec<u8>,
65
66    /// Request to exit with the given error code
67    pub exit: Option<i32>,
68}
69
70impl Output<'_> {
71    /// Prints `stdout` and `stderr` to the console
72    pub fn print(&self) -> std::io::Result<()> {
73        if !self.stdout.is_empty() {
74            let mut stdout = std::io::stdout().lock();
75            stdout.write_all(&self.stdout)?;
76            stdout.flush()?;
77        }
78        if !self.stderr.is_empty() {
79            let mut stderr = std::io::stderr().lock();
80            stderr.write_all(&self.stderr)?;
81            stderr.flush()?;
82        }
83        Ok(())
84    }
85
86    /// Checks the results
87    ///
88    /// `stdout` and `stderr` are printed, and `exit(..)` is called if it has
89    /// been requested by the VM.
90    pub fn check(&self) -> std::io::Result<()> {
91        self.print()?;
92        if let Some(e) = self.exit {
93            log::info!("requested exit ({e})");
94
95            #[cfg(not(target_arch = "wasm32"))]
96            std::process::exit(e);
97
98            #[cfg(target_arch = "wasm32")]
99            return Err(std::io::Error::other("exit requested"));
100        }
101        Ok(())
102    }
103}
104
105/// Handle to the Varvara system
106pub struct Varvara {
107    system: system::System,
108    console: console::Console,
109    datetime: datetime::Datetime,
110    audio: audio::Audio,
111    screen: screen::Screen,
112    mouse: mouse::Mouse,
113    file: file::File,
114    controller: controller::Controller,
115
116    /// Flags indicating if we've already printed a warning about a missing dev
117    already_warned: [bool; 16],
118}
119
120impl Default for Varvara {
121    fn default() -> Self {
122        Self::new()
123    }
124}
125
126impl Device for Varvara {
127    fn deo(&mut self, vm: &mut UxnCore, target: u8) -> bool {
128        match target & 0xF0 {
129            system::SystemPorts::BASE => self.system.deo(vm, target),
130            console::ConsolePorts::BASE => self.console.deo(vm, target),
131            datetime::DatetimePorts::BASE => self.datetime.deo(vm, target),
132            screen::ScreenPorts::BASE => self.screen.deo(vm, target),
133            mouse::MousePorts::BASE => self.mouse.set_active(),
134            f if file::FilePorts::matches(f) => self.file.deo(vm, target),
135            controller::ControllerPorts::BASE => (),
136            a if audio::AudioPorts::matches(a) => self.audio.deo(vm, target),
137
138            // Default case
139            t => self.warn_missing(t),
140        }
141        !self.system.should_exit()
142    }
143    fn dei(&mut self, vm: &mut UxnCore, target: u8) {
144        match target & 0xF0 {
145            system::SystemPorts::BASE => self.system.dei(vm, target),
146            console::ConsolePorts::BASE => self.console.dei(vm, target),
147            datetime::DatetimePorts::BASE => self.datetime.dei(vm, target),
148            screen::ScreenPorts::BASE => self.screen.dei(vm, target),
149            mouse::MousePorts::BASE => self.mouse.set_active(),
150            f if file::FilePorts::matches(f) => (),
151            controller::ControllerPorts::BASE => (),
152            a if audio::AudioPorts::matches(a) => self.audio.dei(vm, target),
153
154            // Default case
155            t => self.warn_missing(t),
156        }
157    }
158}
159
160impl Varvara {
161    /// Builds a new instance of the Varvara peripherals
162    pub fn new() -> Self {
163        Self {
164            console: console::Console::new(),
165            system: system::System::new(),
166            datetime: datetime::Datetime,
167            audio: audio::Audio::new(),
168            screen: screen::Screen::new(),
169            mouse: mouse::Mouse::new(),
170            file: file::File::new(),
171            controller: controller::Controller::new(),
172
173            already_warned: [false; 16],
174        }
175    }
176
177    /// Resets the CPU, loading extra data into expansion memory
178    ///
179    /// Note that the audio stream handles are unchanged, so any audio worker
180    /// threads can continue to run.
181    pub fn reset(&mut self, extra: &[u8]) {
182        self.system.reset(extra);
183        self.console = console::Console::new();
184        self.audio.reset();
185        self.screen = screen::Screen::new();
186        self.mouse = mouse::Mouse::new();
187        self.file = file::File::new();
188        self.controller = controller::Controller::new();
189        self.already_warned.fill(false);
190    }
191
192    /// Checks whether the SHIFT key is currently down
193    fn warn_missing(&mut self, t: u8) {
194        if !self.already_warned[usize::from(t >> 4)] {
195            warn!("unimplemented device {t:#02x}");
196            self.already_warned[usize::from(t >> 4)] = true;
197        }
198    }
199
200    /// Calls the screen vector
201    ///
202    /// This function must be called at 60 Hz
203    pub fn redraw<B: uxn::Backend>(&mut self, vm: &mut Uxn<B>) {
204        let e = self.screen.update(vm);
205        self.process_event(vm, e);
206    }
207
208    /// Sets initial value for `Console/type` based on the presense of arguments
209    ///
210    /// This should be called before running the reset vector
211    pub fn init_args<B>(&mut self, vm: &mut Uxn<B>, args: &[String]) {
212        self.console.set_has_args(vm, !args.is_empty());
213    }
214
215    /// Returns the current output state of the system
216    ///
217    /// This is not idempotent; the output is taken from various accumulators
218    /// and will be empty if this is called multiple times.
219    #[must_use]
220    pub fn output<B>(&mut self, vm: &Uxn<B>) -> Output<'_> {
221        Output {
222            size: self.screen.size(),
223            frame: self.screen.frame(vm),
224            hide_mouse: self.mouse.active(),
225            stdout: self.console.stdout(),
226            stderr: self.console.stderr(),
227            exit: self.system.exit(),
228        }
229    }
230
231    /// Sends arguments to the console device
232    ///
233    /// Leaves the console type set to `stdin`, and returns the current output
234    /// state of the system
235    pub fn send_args<B: uxn::Backend>(
236        &mut self,
237        vm: &mut Uxn<B>,
238        args: &[String],
239    ) -> Output<'_> {
240        for (i, a) in args.iter().enumerate() {
241            self.console.set_type(vm, console::Type::Argument);
242            for c in a.bytes() {
243                self.process_event(vm, self.console.update(vm, c));
244            }
245
246            let ty = if i == args.len() - 1 {
247                console::Type::ArgumentEnd
248            } else {
249                console::Type::ArgumentSpacer
250            };
251            self.console.set_type(vm, ty);
252            self.process_event(vm, self.console.update(vm, b'\n'));
253        }
254        self.console.set_type(vm, console::Type::Stdin);
255        self.output(vm)
256    }
257
258    /// Send a character from the keyboard (controller) device
259    pub fn char<B: uxn::Backend>(&mut self, vm: &mut Uxn<B>, k: u8) {
260        let e = self.controller.char(vm, k);
261        self.process_event(vm, e);
262    }
263
264    /// Press a key on the controller device
265    pub fn pressed<B: uxn::Backend>(
266        &mut self,
267        vm: &mut Uxn<B>,
268        k: Key,
269        repeat: bool,
270    ) {
271        if let Some(e) = self.controller.pressed(vm, k, repeat) {
272            self.process_event(vm, e);
273        }
274    }
275
276    /// Release a key on the controller device
277    pub fn released<B: uxn::Backend>(&mut self, vm: &mut Uxn<B>, k: Key) {
278        if let Some(e) = self.controller.released(vm, k) {
279            self.process_event(vm, e);
280        }
281    }
282
283    /// Send a character from the console device
284    pub fn console<B: uxn::Backend>(&mut self, vm: &mut Uxn<B>, c: u8) {
285        let e = self.console.update(vm, c);
286        self.process_event(vm, e);
287    }
288
289    /// Updates the mouse state
290    pub fn mouse<B: uxn::Backend>(&mut self, vm: &mut Uxn<B>, m: MouseState) {
291        if let Some(e) = self.mouse.update(vm, m) {
292            self.process_event(vm, e);
293        }
294    }
295
296    /// Processes pending audio events
297    pub fn audio<B: uxn::Backend>(&mut self, vm: &mut Uxn<B>) {
298        for i in 0..audio::DEV_COUNT {
299            if let Some(e) = self.audio.update(vm, usize::from(i)) {
300                self.process_event(vm, e);
301            }
302        }
303    }
304
305    /// Processes a single vector event
306    ///
307    /// Events with an unassigned vector (i.e. 0) are ignored
308    fn process_event<B: uxn::Backend>(&mut self, vm: &mut Uxn<B>, e: Event) {
309        if e.vector != 0 {
310            if let Some(d) = e.data {
311                vm.write_dev_mem(d.addr, d.value);
312            }
313            vm.run(self, e.vector);
314            if let Some(d) = e.data
315                && d.clear
316            {
317                vm.write_dev_mem(d.addr, 0);
318            }
319        }
320    }
321
322    /// Returns the set of audio stream data handles
323    pub fn audio_streams(&self) -> [Arc<Mutex<audio::StreamData>>; 4] {
324        [0, 1, 2, 3].map(|i| self.audio.stream(i))
325    }
326
327    /// Sets the global mute flag for audio
328    pub fn audio_set_muted(&mut self, m: bool) {
329        self.audio.set_muted(m)
330    }
331}