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