1#![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
17mod 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#[derive(Copy, Clone, Debug)]
33struct EventData {
34 addr: u8,
35 value: u8,
36 clear: bool,
37}
38
39#[derive(Copy, Clone, Debug)]
41struct Event {
42 pub data: Option<EventData>,
44
45 pub vector: u16,
47}
48
49pub struct Output<'a> {
51 pub size: (u16, u16),
53
54 pub frame: &'a [u8],
56
57 pub hide_mouse: bool,
59
60 pub stdout: Vec<u8>,
62
63 pub stderr: Vec<u8>,
65
66 pub exit: Option<i32>,
68}
69
70impl Output<'_> {
71 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 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
105pub 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 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 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 t => self.warn_missing(t),
156 }
157 }
158}
159
160impl Varvara {
161 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 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 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 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 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 #[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 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 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 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 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 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 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 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 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 pub fn audio_streams(&self) -> [Arc<Mutex<audio::StreamData>>; 4] {
324 [0, 1, 2, 3].map(|i| self.audio.stream(i))
325 }
326
327 pub fn audio_set_muted(&mut self, m: bool) {
329 self.audio.set_muted(m)
330 }
331}