1use std::fmt::Write;
26use std::fmt::{self, Debug, Display, Formatter};
27use std::time::Duration;
28
29use base::prelude::*;
30use std::cmp;
31
32use conv::*;
33use tracing::{Level, event};
34
35use crate::diagnostics::CurrentInstructionDiagnostics;
36
37use super::*;
38use super::{TransferFailed, Unit, UnitStatus};
39
40#[derive(Debug, Clone, Copy, Eq, PartialEq)]
42enum Activity {
43 Stopped,
44 Started,
45}
46
47impl Display for Activity {
48 fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), fmt::Error> {
49 f.write_str(match self {
50 Activity::Stopped => "stopped",
51 Activity::Started => "running",
52 })
53 }
54}
55
56#[derive(Debug, Clone, Copy, Eq, PartialEq)]
57enum Direction {
58 Bin,
61
62 Reel,
65}
66
67impl Display for Direction {
68 fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), fmt::Error> {
69 f.write_str(match self {
70 Direction::Bin => "bin",
71 Direction::Reel => "reel",
72 })
73 }
74}
75
76fn next_line_time(direction: Direction, system_time: &Duration) -> Duration {
77 *system_time
83 + match direction {
84 Direction::Bin => {
85 Duration::from_micros(400)
88 }
89 Direction::Reel => {
90 Duration::from_micros(2500)
93 }
94 }
95}
96
97pub(crate) struct Petr {
98 activity: Activity,
103 direction: Direction,
104 tape_data: Vec<u8>,
105 tape_pos: usize,
106 data: Option<u8>,
107 already_warned_eof: bool,
108 read_failed: bool,
109 overrun: bool,
110 time_of_next_read: Option<Duration>,
111 connected_at_system_time: Option<Duration>,
112 connected_at_elapsed_time: Option<Duration>,
113 rewind_line_counter: usize,
114 mode: Unsigned12Bit,
115}
116
117impl Debug for Petr {
118 fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), fmt::Error> {
119 f.debug_struct("Petr")
120 .field("activity", &self.activity)
121 .field("direction", &self.direction)
122 .field("tape_data", &self.tape_data)
123 .field("tape_pos", &self.tape_pos)
124 .field("data", &self.data)
125 .field("already_warned_eof", &self.already_warned_eof)
126 .field("read_failed", &self.read_failed)
127 .field("overrun", &self.overrun)
128 .field("time_of_next_read", &self.time_of_next_read)
129 .field("connected_at_system_time", &self.connected_at_system_time)
130 .field("connected_at_elapsed_time", &self.connected_at_elapsed_time)
131 .field("rewind_line_counter", &self.rewind_line_counter)
132 .field("mode", &self.mode)
133 .finish_non_exhaustive()
134 }
135}
136
137impl Petr {
138 pub(crate) fn new() -> Petr {
139 Petr {
140 activity: Activity::Stopped,
141 direction: Direction::Bin,
142 tape_data: Vec::new(),
143 tape_pos: 0,
144 data: None,
145 already_warned_eof: false,
146 read_failed: false,
147 overrun: false,
148 time_of_next_read: None,
149 connected_at_system_time: None,
150 connected_at_elapsed_time: None,
151 rewind_line_counter: 0,
152 mode: Unsigned12Bit::ZERO,
153 }
154 }
155
156 fn next_poll_time(&mut self, system_time: &Duration) -> Duration {
157 match self.time_of_next_read {
158 Some(t) => {
159 let result = cmp::max(t, *system_time);
160 if let Some(interval) = result.checked_sub(*system_time) {
161 event!(
162 Level::DEBUG,
163 "next_poll_time: please poll after {interval:?} at system time {t:?}"
164 );
165 }
166 result
167 }
168 None => {
169 let interval = Duration::from_secs(1);
170 let next = *system_time + interval;
171 event!(
172 Level::DEBUG,
173 "next_poll_time: time_of_next_read is None, using default {system_time:?}+{interval:?}={:?}; self={:?}",
174 next,
175 &self
176 );
177 next
178 }
179 }
180 }
181
182 fn do_rewind(&mut self) {
183 match self.rewind_line_counter.checked_sub(1) {
184 None | Some(0) => {
185 event!(Level::INFO, "reached the end mark, reversing direction");
188 assert!(self.direction == Direction::Bin);
189 self.direction = Direction::Reel;
190 }
191 Some(reduced_by_1) => {
192 self.rewind_line_counter = reduced_by_1;
193 event!(
194 Level::DEBUG,
195 "rewound over a line: {} more to go",
196 self.rewind_line_counter
197 );
198 }
199 }
200 }
201
202 fn do_read(&mut self) {
203 match self.tape_data.get(self.tape_pos) {
206 None => {
207 if self.already_warned_eof {
211 event!(
212 Level::DEBUG,
213 "reading again at end-of-file at position {}",
214 self.tape_pos
215 );
216 } else {
217 self.already_warned_eof = true;
218 event!(
219 Level::WARN,
220 "end-of-file on tape input file at position {}",
221 self.tape_pos
222 );
223 }
224 self.time_of_next_read = None;
225 }
226 Some(byte) => {
227 event!(
228 Level::DEBUG,
229 "read a byte at file position {}: {:?}",
230 self.tape_pos,
231 byte
232 );
233 self.already_warned_eof = false;
234 if !self.overrun {
235 self.overrun = self.data.is_some();
236 if self.overrun {
237 event!(Level::WARN, "PETR input overrun");
238 }
239 }
240 self.data = Some(*byte);
241 self.tape_pos += 1;
242 }
243 }
244 }
245
246 fn maybe_simulate_event(&mut self, system_time: &Duration) {
247 event!(
248 Level::TRACE,
249 "maybe_simulate_event: activity={:?}",
250 &self.activity
251 );
252 match self.activity {
253 Activity::Started => {
254 if let Some(t) = self.time_of_next_read
255 && t > *system_time
256 {
257 let to_wait = || t - *system_time;
260 event!(
261 Level::TRACE,
262 "motor running ({}) but next line will not be read for {:?} yet",
263 self.direction,
264 to_wait()
265 );
266 return;
267 }
268 match self.direction {
269 Direction::Bin => {
270 event!(Level::TRACE, "motor running, doing rewind action");
271 self.do_rewind();
272 }
273 Direction::Reel => {
274 event!(Level::TRACE, "motor running, doing read action");
275 self.do_read();
276 }
277 }
278 self.time_of_next_read = match self.activity {
281 Activity::Started => Some(next_line_time(self.direction, system_time)),
282 Activity::Stopped => None,
283 }
284 }
285 Activity::Stopped => {
286 event!(Level::TRACE, "motor stopped, nothing more to simulate");
289 self.time_of_next_read = None;
290 }
291 }
292 }
293
294 fn transfer_mode(&self) -> TransferMode {
295 if self.mode & 0o02 != 0 {
296 TransferMode::Assembly
297 } else {
298 TransferMode::Exchange
299 }
300 }
301}
302
303struct Throughput {
304 lines_per_second: f64,
305 total_seconds: f64,
306}
307
308fn compute_throughput(
309 pos: usize,
310 connect_time: Option<Duration>,
311 now: Duration,
312) -> Option<Throughput> {
313 if let Some(connected_at) = connect_time
314 && let Some(elapsed) = now.checked_sub(connected_at)
315 {
316 let elapsed = elapsed.as_secs_f64();
317
318 #[allow(irrefutable_let_patterns)]
322 if let Ok(n) = f64::value_from(pos) {
323 let throughput = n / elapsed;
324 return Some(Throughput {
325 lines_per_second: throughput,
326 total_seconds: elapsed,
327 });
328 }
329 }
330 None
331}
332
333impl Unit for Petr {
334 fn poll(&mut self, ctx: &Context) -> UnitStatus {
335 let system_time = &ctx.simulated_time;
336 event!(Level::TRACE, "poll called at system time {system_time:?}");
337
338 self.maybe_simulate_event(system_time);
339 let data_ready: bool = self.data.is_some();
340 let poll_after = self.next_poll_time(system_time);
341 event!(Level::TRACE, "PETR poll: poll_after={poll_after:?}");
342 UnitStatus {
343 special: Unsigned12Bit::ZERO,
344 change_flag: if data_ready {
345 Some(FlagChange::Raise("data is ready"))
346 } else {
347 None
348 },
349 buffer_available_to_cpu: data_ready,
350 inability: self.read_failed,
351 missed_data: self.overrun,
352 mode: self.mode,
353 poll_after,
354 is_input_unit: true,
355 }
356 }
357
358 fn connect(&mut self, ctx: &Context, mode: Unsigned12Bit) {
359 let system_time = &ctx.simulated_time;
360 self.connected_at_elapsed_time = Some(ctx.real_elapsed_time);
361 self.connected_at_system_time = Some(ctx.simulated_time);
362 self.direction = if mode & 0o04 != 0 {
363 Direction::Bin
364 } else {
365 Direction::Reel
366 };
367 self.activity = if mode & 0o100 != 0 {
368 self.time_of_next_read = Some(next_line_time(self.direction, system_time));
369 Activity::Started
370 } else {
371 self.time_of_next_read = None;
373 Activity::Stopped
374 };
375 self.tape_pos = 0;
376 self.mode = mode;
377 let transfer_mode_name = match self.transfer_mode() {
378 TransferMode::Assembly => "assembly",
379 TransferMode::Exchange => "exchange",
380 };
381 event!(
382 Level::INFO,
383 "PETR connected; motor {}, direction {}; {} mode {:o} (time_of_next_read={:?})",
384 self.activity,
385 self.direction,
386 transfer_mode_name,
387 self.mode,
388 self.time_of_next_read,
389 );
390 }
391
392 fn transfer_mode(&self) -> TransferMode {
393 self.transfer_mode()
394 }
395
396 fn read(
397 &mut self,
398 ctx: &Context,
399 _diagnostics: &CurrentInstructionDiagnostics,
400 ) -> Result<MaskedWord, TransferFailed> {
401 let system_time = &ctx.simulated_time;
402 match self.data.take() {
403 None => {
404 event!(Level::DEBUG, "no data is ready yet");
405 Err(TransferFailed::BufferNotFree)
406 }
407 Some(byte) => {
408 self.time_of_next_read = Some(next_line_time(self.direction, system_time));
412 event!(Level::DEBUG, "read value {:03o}", byte & 0o77);
413 Ok(MaskedWord {
414 bits: Unsigned36Bit::from(byte & 0o77),
415 mask: u36!(0o77),
416 })
417 }
418 }
419 }
420
421 fn write(
422 &mut self,
423 _ctx: &Context,
424 _source: Unsigned36Bit,
425 diagnostics: &CurrentInstructionDiagnostics,
426 ) -> Result<Option<OutputEvent>, TransferFailed> {
427 unreachable!("attempted to write to the paper tape reader ({diagnostics})")
428 }
429
430 fn name(&self) -> String {
431 "PETR photoelectric paper tape reader".to_string()
432 }
433
434 fn disconnect(&mut self, _ctx: &Context) {
435 event!(Level::INFO, "PETR disconnecting");
436 self.activity = Activity::Stopped;
437 }
438
439 fn on_input_event(
440 &mut self,
441 _ctx: &Context,
442 event: InputEvent,
443 ) -> Result<InputFlagRaised, InputEventError> {
444 if let InputEvent::PetrMountPaperTape { data } = event {
445 event!(Level::DEBUG, "Mounting a tape ({} bytes)", data.len());
446 self.tape_data = data;
447 Ok(InputFlagRaised::No)
451 } else {
452 Err(InputEventError::InputEventNotValidForDevice)
453 }
454 }
455
456 fn text_info(&self, ctx: &Context) -> String {
457 let build = || -> Result<String, std::fmt::Error> {
458 let mut result = String::new();
459 write!(result, "Motor {}", self.activity)?;
460 match self.activity {
461 Activity::Started => {
462 write!(result, " (direction {})", self.direction)?;
463 }
464 Activity::Stopped => (),
465 }
466 write!(result, ". ")?;
467 if self.tape_data.is_empty() {
468 write!(result, "No tape (or blank tape) loaded. ")?;
469 } else {
470 write!(
471 result,
472 "Loaded tape has {} lines, we have read {}. ",
473 self.tape_data.len(),
474 self.tape_pos
475 )?;
476 if let Some(throughput) = compute_throughput(
477 self.tape_pos,
478 self.connected_at_system_time,
479 ctx.simulated_time,
480 ) {
481 write!(
482 result,
483 "Emulated duration {:.1} seconds, so emulated throughput is {:.1} lines/sec. ",
484 throughput.total_seconds, throughput.lines_per_second,
485 )?;
486 }
487 if let Some(throughput) = compute_throughput(
488 self.tape_pos,
489 self.connected_at_elapsed_time,
490 ctx.real_elapsed_time,
491 ) {
492 write!(
493 result,
494 "Real duration {:.1} seconds, so real throughput is {:.1} lines/sec.",
495 throughput.total_seconds, throughput.lines_per_second,
496 )?;
497 }
498 }
499 Ok(result)
500 };
501 build().expect("write! calls on a String should always succeed")
502 }
503}