1mod keyboard;
2
3use std::collections::BTreeMap;
4
5use js_sys::Array;
6use serde::Serialize;
7use tracing::{Level, event};
8use wasm_bindgen::prelude::*;
9
10use base::Unsigned6Bit;
11use cpu::*;
12
13use super::context::make_context;
14use super::io::keyboard::Code;
15use super::samples::{sample_binary_echo, sample_binary_hello};
16
17pub use keyboard::{
18 HtmlCanvas2DPainter, KeyPaintError, SWITCH_TO_FAR, SWITCH_TO_NEAR, draw_keyboard,
19};
20
21#[wasm_bindgen]
22pub fn tx2_load_tape(
23 tx2: &mut Tx2,
24 simulated_time: f64,
25 elapsed_time_secs: f64,
26 data: &[u8],
27) -> bool {
28 let context = make_context(simulated_time, elapsed_time_secs);
29 tx2.mount_paper_tape(&context, data.to_vec()).map_or_else(
30 |e: InputEventError| {
31 event!(Level::ERROR, "failed to load paper tape: {e}");
32 false
33 },
34 |f: InputFlagRaised| f.into(),
35 )
36}
37
38pub(crate) struct EmittedCodes {
39 first: u8,
40 second: Option<u8>,
41 far_now_active: bool,
42}
43
44fn try_u6_from_u8(x: u8) -> Result<Unsigned6Bit, String> {
45 fn failed<E: std::error::Error>(e: E) -> String {
46 format!("failed to map a key code into a six-bit number: {e}")
47 }
48 <Unsigned6Bit as std::convert::TryFrom<u8>>::try_from(x).map_err(failed)
49}
50
51impl EmittedCodes {
52 fn try_to_u6_vec(&self) -> Result<Vec<Unsigned6Bit>, String> {
53 let mut result: Vec<Unsigned6Bit> =
54 Vec::with_capacity(if self.second.is_some() { 2 } else { 1 });
55 result.push(try_u6_from_u8(self.first)?);
56 if let Some(second) = self.second {
57 result.push(try_u6_from_u8(second)?);
58 }
59 Ok(result)
60 }
61}
62
63pub(crate) fn hit_detection_rgb_to_code(
64 far_currently_active: bool,
65 r: u8,
66 g: u8,
67 b: u8,
68) -> Result<Option<EmittedCodes>, String> {
69 match Code::hit_detection_rgb_to_code(r, g, b) {
70 Ok(Some(Code::Far(code))) => Ok(Some(if far_currently_active {
71 EmittedCodes {
72 first: code,
73 second: None,
74 far_now_active: true,
75 }
76 } else {
77 EmittedCodes {
78 first: SWITCH_TO_FAR,
79 second: Some(code),
80 far_now_active: true,
81 }
82 })),
83 Ok(Some(Code::Near(code))) => Ok(Some(if far_currently_active {
84 EmittedCodes {
85 first: SWITCH_TO_NEAR,
86 second: Some(code),
87 far_now_active: false,
88 }
89 } else {
90 EmittedCodes {
91 first: code,
92 second: None,
93 far_now_active: false,
94 }
95 })),
96 Ok(None) => Ok(None),
97 Ok(Some(Code::Unknown)) => {
98 event!(
99 Level::DEBUG,
100 "the user hit a key whose key code we don't know"
101 );
102 Ok(None)
103 }
104 Err(e) => Err(e),
105 }
106}
107
108#[wasm_bindgen]
109pub struct KeystrokeOutcome {
110 consumed: bool,
111 flag_raised: bool,
112 far_keyboard_is_active: bool,
113}
114
115#[wasm_bindgen]
116impl KeystrokeOutcome {
117 #[wasm_bindgen(getter)]
118 pub fn consumed(&self) -> bool {
119 self.consumed
120 }
121
122 #[wasm_bindgen(getter)]
123 pub fn flag_raised(&self) -> bool {
124 self.flag_raised
125 }
126
127 #[wasm_bindgen(getter)]
128 pub fn far_keyboard_is_active(&mut self) -> bool {
129 self.far_keyboard_is_active
130 }
131}
132
133#[wasm_bindgen]
134pub fn tx2_lw_keyboard_click(
135 tx2: &mut Tx2,
136 simulated_time: f64,
137 elapsed_time_secs: f64,
138 unit: u8,
139 far_currently_active: bool,
140 rgb: &js_sys::Uint8ClampedArray,
141) -> KeystrokeOutcome {
142 let context = make_context(simulated_time, elapsed_time_secs);
143 match hit_detection_rgb_to_code(
144 far_currently_active,
145 rgb.at(0).unwrap_or(0),
146 rgb.at(1).unwrap_or(0),
147 rgb.at(2).unwrap_or(0),
148 ) {
149 Ok(Some(codes)) => match try_u6_from_u8(unit) {
150 Ok(unit) => match codes.try_to_u6_vec() {
151 Err(e) => {
152 event!(
153 Level::ERROR,
154 "tx2_lw_keyboard_click: failed to convert input key code: {e}"
155 );
156 KeystrokeOutcome {
157 consumed: false,
158 flag_raised: false,
159 far_keyboard_is_active: codes.far_now_active,
160 }
161 }
162 Ok(data) => match tx2.lw_input(&context, unit, &data) {
163 Ok((consumed, flag_raise)) => KeystrokeOutcome {
164 consumed,
165 flag_raised: flag_raise.into(),
166 far_keyboard_is_active: codes.far_now_active,
167 },
168 Err(e) => {
169 event!(
170 Level::ERROR,
171 "tx2_lw_keyboard_click: failed to accept input key: {e}"
172 );
173 KeystrokeOutcome {
174 consumed: false,
175 flag_raised: false,
176 far_keyboard_is_active: codes.far_now_active,
177 }
178 }
179 },
180 },
181 Err(_) => {
182 event!(
183 Level::WARN,
184 "tx2_lw_keyboard_click: unit {unit:o} is out of range"
185 );
186 KeystrokeOutcome {
187 consumed: false,
188 flag_raised: false,
189 far_keyboard_is_active: codes.far_now_active,
190 }
191 }
192 },
193 Ok(None) => {
194 KeystrokeOutcome {
196 consumed: true,
197 flag_raised: false,
198 far_keyboard_is_active: far_currently_active,
199 }
200 }
201 Err(_) => KeystrokeOutcome {
202 consumed: true,
203 flag_raised: false,
204 far_keyboard_is_active: far_currently_active,
205 },
206 }
207}
208
209#[wasm_bindgen]
210pub fn get_builtin_sample_tape(sample_name: &str) -> Result<Vec<u8>, String> {
211 match sample_name {
212 "echo" => Ok(sample_binary_echo()),
213 "hello" => Ok(sample_binary_hello()),
214 _ => Err(format!("unknown sample file '{sample_name}'")),
215 }
216 .map(|data| data.to_vec())
217}
218
219#[derive(Debug, Serialize)]
220struct SerializableExtendedUnitState {
221 pub flag: bool,
222 pub connected: bool,
223 pub in_maintenance: bool,
224 pub name: String,
225 pub text_info: String,
226 pub status: Option<ExtendedConnectedUnitStatus>,
229 pub index_value: i32,
230}
231
232impl From<ExtendedUnitState> for SerializableExtendedUnitState {
233 fn from(input: cpu::ExtendedUnitState) -> Self {
234 SerializableExtendedUnitState {
235 flag: input.flag,
236 connected: input.connected,
237 in_maintenance: input.in_maintenance,
238 name: input.name,
239 text_info: input.text_info,
240 status: input.status,
241 index_value: i32::from(input.index_value), }
243 }
244}
245
246#[derive(Debug, Serialize)]
247pub struct UnitState {
248 unit: u8,
249 unit_state: SerializableExtendedUnitState,
250}
251
252#[wasm_bindgen]
253pub fn tx2_drain_device_changes(
254 tx2: &mut Tx2,
255 simulated_system_time_secs: f64,
256 elapsed_time_secs: f64,
257) -> Result<JsValue, String> {
258 let context = make_context(simulated_system_time_secs, elapsed_time_secs);
259 match tx2.drain_device_changes(&context) {
260 Ok(changes) => {
261 let change_map: BTreeMap<u8, UnitState> = changes
262 .into_iter()
263 .map(|(unit, state)| {
264 (
265 u8::from(unit),
266 UnitState {
267 unit: unit.into(),
268 unit_state: state.into(),
269 },
270 )
271 })
272 .collect();
273 serde_wasm_bindgen::to_value(&change_map).map_err(|e| e.to_string())
274 }
275 Err(e) => Err(e.to_string()),
276 }
277}
278
279#[wasm_bindgen]
280pub fn tx2_device_statuses(
281 tx2: &mut Tx2,
282 simulated_system_time_secs: f64,
283 elapsed_time_secs: f64,
284) -> Array {
285 let context = make_context(simulated_system_time_secs, elapsed_time_secs);
286 match tx2.sequence_statuses(&context) {
287 Err(e) => {
288 panic!("tx2_device_statuses: failed: {e}");
289 }
290 Ok(statuses) => statuses
291 .into_iter()
292 .map(|(unit, status)| {
293 let status: SerializableExtendedUnitState = status.into();
294 event!(
295 Level::DEBUG,
296 "tx2_device_statuses: serializable status for unit {unit} is {status:?}"
297 );
298 let tmp = UnitState {
299 unit: u8::from(unit),
300 unit_state: status,
301 };
302 match serde_wasm_bindgen::to_value(&tmp) {
303 Ok(jsval) => jsval,
304 Err(e) => {
305 panic!("failed to convert {tmp:?} to JsValue: {e}");
306 }
307 }
308 })
309 .collect(),
310 }
311}