use super::Session;
use crate::{
error::Error,
protocol::{decode::Samples, image::Layout, window::Window},
scan::pass::{self, Pass, Progress},
session::window::Started,
};
use std::{
collections::VecDeque,
ops::ControlFlow,
sync::{
atomic::{AtomicU64, Ordering},
mpsc::{self, Receiver, Sender},
},
thread,
time::{Duration, Instant},
};
use tracing::*;
const POOL: usize = 3;
enum Chunk {
Data(Vec<u8>),
End,
Failed(Error),
}
struct TruncationState {
line: usize,
reading: u32,
offset_in_reading: usize,
}
impl TruncationState {
fn new() -> Self {
Self {
line: 0,
reading: 0,
offset_in_reading: 0,
}
}
}
fn strip_truncation(buf: &mut Vec<u8>, state: &mut TruncationState, layout: &Layout) {
let rows = usize::from(layout.packed_rows);
let readings = layout.readings();
let total_lines = layout.lines as usize;
let first_line = layout.truncated_lines_frame.0 as usize;
let last_line = total_lines - layout.truncated_lines_frame.1 as usize;
let mut read = 0;
let mut write = 0;
while read < buf.len() {
let reading_bytes = (layout.bytes_per_reading(state.reading) as usize * rows).max(1);
let (first_bytes, last_bytes) = layout.truncated_bytes(state.reading);
let (first_bytes, last_bytes) = (first_bytes as usize * rows, last_bytes as usize * rows);
let remaining_in_reading = reading_bytes.saturating_sub(state.offset_in_reading);
let n = remaining_in_reading.min(buf.len() - read);
let line = state.line;
if line >= first_line && line < last_line {
let reading_start = state.offset_in_reading;
let reading_end = state.offset_in_reading + n;
let keep_start = reading_start.max(first_bytes);
let keep_end = reading_end.min(reading_bytes.saturating_sub(last_bytes));
if keep_start < keep_end {
let src_start = read + (keep_start - reading_start);
let src_end = read + (keep_end - reading_start);
let len = src_end - src_start;
buf.copy_within(src_start..src_end, write);
write += len;
}
}
read += n;
state.offset_in_reading += n;
if state.offset_in_reading == reading_bytes {
state.offset_in_reading = 0;
state.reading += 1;
if state.reading == readings {
state.reading = 0;
state.line += 1;
}
}
}
buf.truncate(write);
}
impl Session {
pub fn start_pass(&mut self, windows: &[Window], timeout: Duration) -> Result<Started, Error> {
for w in windows {
self.set_window(w)?;
}
let started = self.scan(windows)?;
let waited = Instant::now();
self.test_unit_ready(timeout)?;
debug!(ready_in = ?waited.elapsed(), "scan ready");
Ok(started)
}
pub fn scan_pass(
&mut self,
windows: &[Window],
timeout: Duration,
samples: &mut Samples,
) -> Result<Pass, Error> {
self.scan_pass_with(windows, timeout, samples, |_| ControlFlow::Continue(()))
}
pub fn scan_pass_with(
&mut self,
windows: &[Window],
timeout: Duration,
samples: &mut Samples,
mut on: impl FnMut(Progress) -> ControlFlow<()>,
) -> Result<Pass, Error> {
let started = self.start_pass(windows, timeout)?;
let layout = started.layout.clone();
let total = layout.total_bytes();
let curves = self.curves();
let mut decoder = match pass::decoder(&layout, curves.as_deref()) {
Ok(decoder) => decoder,
Err(e) => {
self.abandon_scan();
return Err(e);
}
};
samples.resize_for(&decoder);
let timing = Timing::default();
let mut decoding = Duration::ZERO;
let mut idle = Duration::ZERO;
let mut truncation = TruncationState::new();
let reader_layout = layout.clone();
thread::scope(|scope| {
let (full_tx, full_rx) = mpsc::channel::<Chunk>();
let (empty_tx, empty_rx) = mpsc::channel::<Vec<u8>>();
let timing = &timing;
scope.spawn(move || read_chunks(self, &reader_layout, &full_tx, &empty_rx, timing));
let mut out = Ok(());
let mut bytes = 0u64;
loop {
let waited = Instant::now();
let msg = full_rx.recv();
idle += waited.elapsed();
let mut chunk = match msg {
Ok(Chunk::Data(buf)) => buf,
Ok(Chunk::End) | Err(_) => break,
Ok(Chunk::Failed(e)) => {
out = Err(e);
break;
}
};
bytes += chunk.len() as u64;
strip_truncation(&mut chunk, &mut truncation, &layout);
let pushed = Instant::now();
let decoded = decoder.push(&chunk, samples);
decoding += pushed.elapsed();
let _ = empty_tx.send(chunk);
if let Err(e) = decoded {
out = Err(e);
break;
}
let flow = on(Progress {
bytes,
total,
blocks: decoder.decoded(),
});
if flow.is_break() {
out = Err(Error::Cancelled);
break;
}
}
out
})?;
debug!(
blocks = decoder.decoded(),
complete = decoder.complete(),
chunks = Timing::get(&timing.chunks),
bytes = Timing::get(&timing.bytes),
read_ms = Timing::get(&timing.read) / 1_000_000,
starved_ms = Timing::get(&timing.starved) / 1_000_000,
decode_ms = decoding.as_millis(),
idle_ms = idle.as_millis(),
"pass"
);
let (rows, cols) = decoder.shape();
Ok(Pass {
layout: started.layout,
cooperation: started.cooperations,
complete: decoder.complete(),
blocks: decoder.decoded(),
rows,
cols,
})
}
pub fn scan_thumbnail(&mut self, samples: &mut Samples) -> Result<Pass, Error> {
self.scan_thumbnail_with(samples, |_| ControlFlow::Continue(()))
}
pub fn scan_thumbnail_with(
&mut self,
samples: &mut Samples,
on: impl FnMut(Progress) -> ControlFlow<()>,
) -> Result<Pass, Error> {
if !crate::scan::thumbnail::available(self.capabilities()) {
return Err(Error::Unsupported {
op: "thumbnail",
reason: "this unit and adapter do not offer thumbnail scanning".into(),
});
}
let windows = crate::scan::thumbnail::windows(self.capabilities())?;
let windows = self.seed_white_balance(&windows)?;
self.scan_pass_with(&windows, THUMBNAIL_TIMEOUT, samples, on)
}
}
const THUMBNAIL_TIMEOUT: Duration = Duration::from_secs(600);
#[derive(Default)]
struct Timing {
read: AtomicU64,
starved: AtomicU64,
chunks: AtomicU64,
bytes: AtomicU64,
}
impl Timing {
fn add(counter: &AtomicU64, by: u64) {
counter.fetch_add(by, Ordering::Relaxed);
}
fn get(counter: &AtomicU64) -> u64 {
counter.load(Ordering::Relaxed)
}
}
fn read_chunks(
session: &mut Session,
layout: &Layout,
full: &Sender<Chunk>,
empty: &Receiver<Vec<u8>>,
timing: &Timing,
) {
let mut chunks = match session.image_chunks(layout) {
Ok(chunks) => chunks,
Err(e) => {
let _ = full.send(Chunk::Failed(e));
let _ = full.send(Chunk::End);
return;
}
};
let mut pool: VecDeque<Vec<u8>> = (0..POOL).map(|_| vec![0u8; chunks.capacity()]).collect();
loop {
let mut buf = match pool.pop_front() {
Some(buf) => buf,
None => {
let waited = Instant::now();
let buf = match empty.recv() {
Ok(buf) => {
trace!("got empty buffer");
buf
}
Err(_) => return,
};
Timing::add(&timing.starved, waited.elapsed().as_nanos() as u64);
buf
}
};
let reading = Instant::now();
let filled = chunks.fill(&mut buf);
Timing::add(&timing.read, reading.elapsed().as_nanos() as u64);
match filled {
Some(Ok(got)) => {
Timing::add(&timing.chunks, 1);
Timing::add(&timing.bytes, got as u64);
}
Some(Err(e)) => {
let _ = full.send(Chunk::Failed(e));
let _ = full.send(Chunk::End);
return;
}
None => {
let _ = full.send(Chunk::End);
return;
}
}
if full.send(Chunk::Data(buf)).is_err() {
return;
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::protocol::image::Layout;
fn layout(readings: u8) -> Layout {
Layout {
lines: 2,
readings_per_line: readings,
truncated_bytes_line: (0, 3),
..Layout::single_line(4, 2, vec![1, 2, 3])
}
}
fn wire(l: &Layout) -> Vec<u8> {
let mut wire = Vec::new();
let mut n = 0u8;
for r in 0..l.readings() {
let pad = l.truncated_bytes(r).1 as usize;
for _ in 0..l.bytes_per_reading(r) as usize - pad {
wire.push(n);
n = n.wrapping_add(1);
}
wire.extend(vec![0xFF; pad]);
}
wire
}
#[test]
fn each_reading_of_a_line_is_stripped_on_its_own() {
let l = layout(2);
let mut buf = wire(&l);
assert_eq!(buf.len(), 54);
strip_truncation(&mut buf, &mut TruncationState::new(), &l);
assert_eq!(buf.len(), 48);
assert!(!buf.contains(&0xFF));
assert_eq!(buf, (0..48).collect::<Vec<u8>>());
}
#[test]
fn a_chunk_that_ends_inside_a_reading_picks_up_where_it_left_off() {
let l = layout(2);
let whole = wire(&l);
let mut state = TruncationState::new();
let mut out = Vec::new();
for part in whole.chunks(7) {
let mut buf = part.to_vec();
strip_truncation(&mut buf, &mut state, &l);
out.extend_from_slice(&buf);
}
assert_eq!(out, (0..48).collect::<Vec<u8>>());
}
#[test]
fn a_reading_with_infrared_strips_the_count_of_its_own() {
let l = Layout {
lines: 2,
readings_per_line: 2,
truncated_bytes_line: (0, 3),
truncated_bytes_once: (0, 5),
..Layout::single_line(4, 2, vec![9, 1, 2, 3])
};
let mut buf = wire(&l);
assert_eq!(buf.len(), 37 + 27);
strip_truncation(&mut buf, &mut TruncationState::new(), &l);
assert_eq!(buf, (0..56).collect::<Vec<u8>>());
}
#[test]
fn one_reading_a_line_is_the_line_itself() {
let l = layout(1);
let mut buf = wire(&l);
assert_eq!(buf.len(), 27);
strip_truncation(&mut buf, &mut TruncationState::new(), &l);
assert_eq!(buf, (0..24).collect::<Vec<u8>>());
}
}