1use std::cell::RefCell;
13use std::fmt::Debug;
14use std::rc::Rc;
15use std::time::Duration;
16
17use super::super::context::Context;
18use super::super::diagnostics::CurrentInstructionDiagnostics;
19use super::super::event::{InputEvent, InputEventError, OutputEvent};
20use super::super::io::{FlagChange, InputFlagRaised, TransferFailed, Unit, UnitStatus};
21use super::super::types::*;
22use super::super::{Alarm, AlarmDetails};
23use base::charset::LincolnStateTextInfo;
24#[cfg(test)]
25use base::charset::LwKeyboardCase;
26use base::charset::{LincolnState, lincoln_char_to_described_char, lincoln_writer_state_update};
27use base::prelude::*;
28use tracing::{Level, event};
29
30#[cfg(test)]
31use base::charset::{Colour, DescribedChar, LincolnChar, Script};
32
33const CHAR_TRANSMIT_TIME: Duration = Duration::from_millis(100);
34const LATER: Duration = Duration::from_secs(300);
35
36#[derive(Debug)]
37pub(crate) struct LincolnWriterOutput {
38 unit: Unsigned6Bit,
39 mode: Unsigned12Bit,
40 connected: bool,
41 transmit_will_be_finished_at: Option<Duration>,
42 state: Rc<RefCell<LincolnState>>,
43}
44
45impl LincolnWriterOutput {
46 pub(crate) fn new(unit: Unsigned6Bit, state: Rc<RefCell<LincolnState>>) -> LincolnWriterOutput {
47 LincolnWriterOutput {
48 unit,
49 mode: Unsigned12Bit::ZERO,
50 connected: false,
51 transmit_will_be_finished_at: None,
52 state,
53 }
54 }
55
56 fn lw_number(&self) -> u8 {
57 match u8::from(self.unit) {
58 0o66 => 1,
59 0o72 => 2,
60 n => n,
61 }
62 }
63
64 fn make_alarm(&self, details: AlarmDetails) -> Alarm {
65 Alarm {
66 sequence: Some(self.unit),
67 details,
68 }
69 }
70}
71
72impl Unit for LincolnWriterOutput {
73 fn poll(&mut self, ctx: &Context) -> UnitStatus {
74 let (transmitting, next_poll) = match self.transmit_will_be_finished_at {
75 Some(d) if d > ctx.simulated_time => {
76 event!(
77 Level::TRACE,
78 "still transmitting; remaining transmit time is {:?}",
79 d - ctx.simulated_time
80 );
81 (true, d)
82 }
83 None => {
84 event!(Level::TRACE, "no transmit has yet been started");
85 (false, ctx.simulated_time + LATER)
86 }
87 Some(d) => {
88 event!(
89 Level::TRACE,
90 "transmission completed {:?} ago, ready to transmit",
91 (ctx.simulated_time - d)
92 );
93 (false, ctx.simulated_time + LATER)
94 }
95 };
96 let change_flag = if !self.connected || transmitting {
100 None
101 } else {
102 Some(FlagChange::Raise(
103 "(still or newly) connected and not already transmitting",
104 ))
105 };
106 event!(
107 Level::TRACE,
108 "connected: {}, flag: {:?}",
109 self.connected,
110 change_flag
111 );
112 UnitStatus {
113 special: Unsigned12Bit::ZERO,
114 change_flag,
115 buffer_available_to_cpu: !transmitting,
116 inability: false,
117 missed_data: false,
118 mode: self.mode,
119 poll_after: next_poll,
120 is_input_unit: false,
121 }
122 }
123
124 fn connect(&mut self, _ctx: &Context, mode: base::Unsigned12Bit) {
125 event!(Level::INFO, "{} connected", self.name(),);
126 self.connected = true;
127 self.mode = mode;
128 }
129
130 fn read(
131 &mut self,
132 _ctx: &Context,
133 diags: &CurrentInstructionDiagnostics,
134 ) -> Result<MaskedWord, TransferFailed> {
135 unreachable!("attempted to read from an output device (executing {diags})")
136 }
137
138 fn write(
139 &mut self,
140 ctx: &Context,
141 source: base::Unsigned36Bit,
142 diagnostics: &CurrentInstructionDiagnostics,
143 ) -> Result<Option<OutputEvent>, TransferFailed> {
144 match self.state.try_borrow_mut() {
145 Ok(mut state) => {
146 match self.transmit_will_be_finished_at {
147 Some(t) if t > ctx.simulated_time => {
148 event!(
149 Level::DEBUG,
150 "cannot complete TSD, we are already transmitting"
151 );
152 return Err(TransferFailed::BufferNotFree);
153 }
154 None => {
155 event!(Level::TRACE, "this is the unit's first transmit operation");
156 }
157 Some(then) => {
158 let idle_for = ctx.simulated_time - then;
159 event!(
160 Level::TRACE,
161 "ready to transmit more data (and have been for {idle_for:?}"
162 );
163 }
164 }
165 let done_at = ctx.simulated_time + CHAR_TRANSMIT_TIME;
166 event!(
167 Level::DEBUG,
168 "beginning new transmit operation at {:?}, it will complete at {:?}",
169 &ctx.simulated_time,
170 &done_at
171 );
172 self.transmit_will_be_finished_at = Some(done_at);
173 let char_data = Unsigned6Bit::try_from(u64::from(source) & 0o77)
174 .expect("item should only have six value bits (this is a bug)");
175 match lincoln_char_to_described_char(char_data, &mut state) {
176 None => Ok(None),
177 Some(described_char) => Ok(Some(OutputEvent::LincolnWriterPrint {
178 unit: self.unit,
179 ch: described_char,
180 })),
181 }
182 }
183 Err(e) => Err(TransferFailed::Alarm(self.make_alarm(AlarmDetails::BUGAL {
184 activity: crate::alarm::BugActivity::Io,
185 diagnostics: diagnostics.clone(),
186 message: format!(
187 "attempted to transmit on unit {:o} while the Lincoln Writer state is being mutated by receive path: {e}",
188 self.unit,
189 )
190 })))
191 }
192 }
193
194 fn name(&self) -> String {
195 format!("Lincoln Writer output {:2o}", self.lw_number())
196 }
197
198 fn transfer_mode(&self) -> crate::TransferMode {
199 TransferMode::Exchange
200 }
201
202 fn disconnect(&mut self, _ctx: &Context) {
203 self.connected = false;
204 }
205
206 fn on_input_event(
207 &mut self,
208 _ctx: &Context,
209 _event: crate::event::InputEvent,
210 ) -> Result<InputFlagRaised, InputEventError> {
211 Ok(InputFlagRaised::No)
213 }
214
215 fn text_info(&self, _ctx: &Context) -> String {
216 let maybe_transmitting = if self.transmit_will_be_finished_at.is_some() {
219 "Transmitting."
220 } else {
221 "Idle."
222 };
223 match self.state.try_borrow() {
224 Ok(state) => {
225 let info: LincolnStateTextInfo = (&*state).into();
226 format!(
227 "{} {}. {}. {}.",
228 maybe_transmitting, info.script, info.case, info.colour
229 )
230 }
231 Err(_) => maybe_transmitting.to_string(),
232 }
233 }
234}
235
236#[cfg(test)]
237fn check_output(
238 writer: &mut LincolnWriterOutput,
239 when: Duration,
240 out: Unsigned6Bit,
241 expected_output: &Option<DescribedChar>,
242 expected_state: &LincolnState,
243 actual_state: &Rc<RefCell<LincolnState>>,
244) {
245 use base::prelude::{Address, Instruction};
246
247 let context = Context {
248 simulated_time: when,
249 real_elapsed_time: when,
250 };
251 let diagnostics = CurrentInstructionDiagnostics {
252 current_instruction: Instruction::invalid(),
253 instruction_address: Address::ZERO,
254 };
255
256 match (
257 expected_output,
258 writer.write(&context, out.into(), &diagnostics),
259 ) {
260 (Some(expected_output), Ok(Some(OutputEvent::LincolnWriterPrint { unit: _, ch }))) => {
261 if &ch != expected_output {
262 panic!(
263 "output of code {out:o} expected to generate character {expected_output:?}, actually generated {ch:?}"
264 );
265 }
266 }
267 (None, Ok(None)) => (),
268 (Some(expected), Ok(None)) => {
269 panic!(
270 "printing code {out:o} produced no output event, but should have produced {expected:?}"
271 );
272 }
273 (None, Ok(Some(actual))) => {
274 panic!(
275 "printing code {out:o} should have produced no output event, but actually produced {actual:?}"
276 );
277 }
278 (_, Err(e)) => {
279 panic!("output transfer failed {e:?}");
280 }
281 }
282 let actual = actual_state.borrow();
283 if &*actual != expected_state {
284 panic!(
285 "output of code {out:o} expected to generate state {expected_state:?}, actual state is {actual:?}"
286 );
287 }
288}
289
290#[test]
291fn lw_output_space() {
292 let unit = u6!(0o66);
293 let state = Rc::new(RefCell::new(LincolnState::default()));
294 let mut lw_out = LincolnWriterOutput::new(unit, state.clone());
295
296 check_output(
297 &mut lw_out,
298 Duration::from_millis(0),
299 u6!(0o70), &Some(DescribedChar {
301 base_char: LincolnChar::UnicodeBaseChar(' '),
302 unicode_representation: Some(' '),
303 attributes: LincolnState {
304 script: Script::Normal,
305 case: LwKeyboardCase::Lower,
306 colour: Colour::Black,
307 },
308 advance: true,
309 label_matches_unicode: false,
310 }),
311 &LincolnState::default(),
312 &state,
313 );
314}
315
316#[test]
317fn lw_output_cr_resets_state() {
318 let unit = u6!(0o66);
319 let state = Rc::new(RefCell::new(LincolnState::default()));
320 let mut lw_out = LincolnWriterOutput::new(unit, state.clone());
321
322 check_output(
323 &mut lw_out,
324 Duration::from_millis(100),
325 u6!(0o64), &None, &LincolnState {
328 script: Script::Super,
329 case: LwKeyboardCase::Lower,
330 colour: Colour::Black,
331 },
332 &state,
333 );
334 check_output(
335 &mut lw_out,
336 Duration::from_millis(200),
337 u6!(0o67), &None, &LincolnState {
340 script: Script::Super, case: LwKeyboardCase::Lower,
342 colour: Colour::Red,
343 },
344 &state,
345 );
346 check_output(
347 &mut lw_out,
348 Duration::from_millis(300),
349 u6!(0o26), &Some(DescribedChar {
351 base_char: LincolnChar::UnicodeBaseChar('G'),
352 unicode_representation: Some('ᴳ'),
353 attributes: LincolnState {
354 script: Script::Super,
355 case: LwKeyboardCase::Lower,
356 colour: Colour::Red,
357 },
358 advance: true,
359 label_matches_unicode: true,
360 }),
361 &LincolnState {
362 script: Script::Super,
363 case: LwKeyboardCase::Lower,
364 colour: Colour::Red,
365 },
366 &state,
367 );
368
369 check_output(
372 &mut lw_out,
373 Duration::from_millis(400),
374 u6!(0o60), &Some(DescribedChar {
376 base_char: LincolnChar::UnicodeBaseChar('\r'),
377 unicode_representation: Some('\r'),
378 attributes: LincolnState {
379 script: Script::Normal,
380 case: LwKeyboardCase::Lower,
381 colour: Colour::Red,
384 },
385 advance: true,
386 label_matches_unicode: false,
387 }),
388 &LincolnState {
389 script: Script::Normal,
390 case: LwKeyboardCase::Lower,
391 colour: Colour::Red,
392 },
393 &state,
394 );
395}
396
397#[derive(Debug)]
398pub(crate) struct LincolnWriterInput {
399 unit: Unsigned6Bit,
400 mode: Unsigned12Bit,
401 connected: bool,
402 data: Vec<Unsigned6Bit>,
403 state: Rc<RefCell<LincolnState>>,
404}
405
406impl LincolnWriterInput {
407 pub(crate) fn new(unit: Unsigned6Bit, state: Rc<RefCell<LincolnState>>) -> LincolnWriterInput {
408 LincolnWriterInput {
409 unit,
410 mode: Unsigned12Bit::ZERO,
411 connected: false,
412 data: Vec::new(),
413 state,
414 }
415 }
416
417 fn lw_number(&self) -> u8 {
418 match u8::from(self.unit) {
419 0o65 => 1,
420 0o71 => 2,
421 n => n,
422 }
423 }
424}
425
426impl Unit for LincolnWriterInput {
427 fn poll(&mut self, ctx: &Context) -> UnitStatus {
428 let data_available = !self.data.is_empty();
429 let change_flag: Option<FlagChange> = if self.connected {
430 if data_available {
431 event!(
432 Level::DEBUG,
433 "LW input {:o}: connected and data is ready; raising flag",
434 self.unit
435 );
436 Some(FlagChange::Raise("keyboard data is ready")) } else {
438 event!(
439 Level::TRACE,
440 "LW input {:o}: connected but no data ready, will not raise flag",
441 self.unit
442 );
443 None }
445 } else {
446 event!(
447 Level::TRACE,
448 "LW input {:o}: not connected, will not raise flag",
449 self.unit
450 );
451 None };
453
454 UnitStatus {
455 special: Unsigned12Bit::ZERO,
456 change_flag,
457 buffer_available_to_cpu: self.connected && data_available,
458 inability: false,
459 missed_data: false,
460 mode: self.mode,
461 poll_after: ctx.simulated_time + LATER,
467 is_input_unit: true,
468 }
469 }
470
471 fn text_info(&self, _ctx: &Context) -> String {
472 match self.data.as_slice() {
475 [] => "Idle.".to_string(),
476 [one] => {
477 format!("Input available: {one:o}.")
478 }
479 many => {
480 let len = many.len();
483 format!("Multiple ({len}) inputs available!")
484 }
485 }
486 }
487
488 fn connect(&mut self, _ctx: &Context, mode: Unsigned12Bit) {
489 self.mode = mode;
490 self.connected = true;
491 }
492
493 fn disconnect(&mut self, _ctx: &Context) {
494 self.connected = false;
495 }
496
497 fn transfer_mode(&self) -> TransferMode {
498 TransferMode::Exchange
499 }
500
501 fn read(
502 &mut self,
503 _ctx: &Context,
504 _diags: &CurrentInstructionDiagnostics,
505 ) -> Result<MaskedWord, TransferFailed> {
506 event!(
507 Level::DEBUG,
508 "read from LW input device having state {self:?}"
509 );
510 if self.data.is_empty() {
511 Err(TransferFailed::BufferNotFree)
512 } else {
513 Ok(MaskedWord {
514 bits: Unsigned36Bit::from(self.data.remove(0)),
515 mask: u36!(0o77),
516 })
517 }
518 }
519
520 fn write(
521 &mut self,
522 _ctx: &Context,
523 _source: Unsigned36Bit,
524 diagnostics: &CurrentInstructionDiagnostics,
525 ) -> Result<Option<OutputEvent>, TransferFailed> {
526 unreachable!("attempted to write to an input device (while executing {diagnostics})")
527 }
528
529 fn name(&self) -> String {
530 format!("Lincoln Writer input {:2o}", self.lw_number())
531 }
532
533 fn on_input_event(
560 &mut self,
561 _ctx: &Context,
562 event: crate::InputEvent,
563 ) -> Result<InputFlagRaised, InputEventError> {
564 event!(
565 Level::DEBUG,
566 "LW input {:o} processing input event: {:?}",
567 self.unit,
568 &event
569 );
570 let result: Result<InputFlagRaised, InputEventError> =
571 if let InputEvent::LwKeyboardInput { data } = event {
572 match (self.data.is_empty(), data.as_slice()) {
573 (_, []) => Ok(InputFlagRaised::No), (false, _) => Err(InputEventError::BufferUnavailable),
575 (true, items) => {
576 match self.state.try_borrow_mut() {
577 Ok(mut state) => {
578 if !self.data.is_empty() {
579 return Err(InputEventError::Alarm(Alarm {
580 sequence: None,
581 details: AlarmDetails::MISAL {
582 affected_unit: self.unit,
583 },
584 }));
585 }
586 for item in items {
587 lincoln_writer_state_update(*item, &mut state);
593 self.data.push(*item);
594 }
595 Ok(InputFlagRaised::Yes)
596 }
597 Err(_) => Err(InputEventError::InvalidReentrantCall),
598 }
599 }
600 }
601 } else {
602 Err(InputEventError::InputEventNotValidForDevice)
603 };
604 event!(
605 Level::DEBUG,
606 "LW input {:o} completing input event, data is now {:?}, result is {:?}",
607 self.unit,
608 self.data,
609 &result
610 );
611 result
612 }
613}