1use crate::destination::{Destination, Forward};
25use alloc::{boxed::Box, collections::VecDeque, sync::Arc};
26use core::{
27 error::Error,
28 fmt,
29 num::{NonZeroU32, NonZeroUsize},
30};
31
32use std::{
33 fs::File,
34 io::{self, Read, Seek, SeekFrom, Write},
35};
36use zencodec::output_patch::OutputAccess;
37use zencodec::{
38 At,
39 encode::{
40 DynEncodeJob, DynEncoder, EncodeDeclaration, EncodeJob, EncodeOutput, EncodePlan,
41 EncodePlanError, Encoder, InputRequirement,
42 },
43 operation::{
44 OperationPolicy, OperationReport, OutputSchedule, OutputScheduleFacts, OutputScheduleOffer,
45 OutputSource, PolicySet, StagingFacts, StagingKind, resolve_output_schedule,
46 },
47 storage::{OwnedStorage, StorageBudget},
48};
49use zenpixels::{ColorContext, PixelDescriptor, PixelSlice, PixelSliceMut, sample::ExactDepth};
50
51mod planned;
52
53#[derive(Clone, Debug, PartialEq, Eq)]
55pub struct Description {
56 pub width: u32,
58 pub height: u32,
60 pub descriptor: PixelDescriptor,
62 pub color_context: Option<Arc<ColorContext>>,
64 pub exact_depth: Option<ExactDepth>,
69}
70impl Description {
71 pub const fn new(width: u32, height: u32, descriptor: PixelDescriptor) -> Self {
73 Self {
74 width,
75 height,
76 descriptor,
77 color_context: None,
78 exact_depth: None,
79 }
80 }
81 pub fn with_color_context(mut self, context: Arc<ColorContext>) -> Self {
83 self.color_context = Some(context);
84 self
85 }
86
87 pub const fn with_exact_depth(mut self, exact: ExactDepth) -> Self {
91 self.exact_depth = Some(exact);
92 self
93 }
94}
95pub trait Source {
97 type Error: Error + Send + Sync + 'static;
99 fn description(&self) -> Description;
101 fn mechanism(&self) -> OutputSource {
103 OutputSource::PullRows
104 }
105 fn borrowed_pixels(&self) -> Option<PixelSlice<'_>> {
109 None
110 }
111 fn supports_positional_replay(&self) -> bool {
115 false
116 }
117 fn replay_identity(&self) -> Result<Option<[u64; 4]>, Self::Error> {
120 Ok(None)
121 }
122 fn read_rows(
128 &mut self,
129 first: u32,
130 rows: PixelSliceMut<'_>,
131 pulse: Option<&dyn zencodec::Pulse>,
132 ) -> Result<(), Self::Error>;
133}
134pub struct Raster<'a>(PixelSlice<'a>);
139impl<'a> Raster<'a> {
140 pub const fn new(pixels: PixelSlice<'a>) -> Self {
142 Self(pixels)
143 }
144}
145impl Source for Raster<'_> {
146 type Error = zencodec::CodecError;
147 fn description(&self) -> Description {
148 let mut desc = Description::new(self.0.width(), self.0.rows(), self.0.descriptor());
149 desc.color_context = self.0.color_context().cloned();
150 desc.exact_depth = self.0.exact_depth();
151 desc
152 }
153 fn mechanism(&self) -> OutputSource {
154 OutputSource::BorrowedRaster
155 }
156 fn borrowed_pixels(&self) -> Option<PixelSlice<'_>> {
157 Some(self.0.clone())
158 }
159 fn read_rows(
160 &mut self,
161 first: u32,
162 mut rows: PixelSliceMut<'_>,
163 pulse: Option<&dyn zencodec::Pulse>,
164 ) -> Result<(), Self::Error> {
165 let pulse = pulse.unwrap_or(&zencodec::NoPulse);
166 if rows.width() != self.0.width()
167 || rows.descriptor() != self.0.descriptor()
168 || rows.color_context() != self.0.color_context()
169 || first
170 .checked_add(rows.rows())
171 .is_none_or(|end| end > self.0.rows())
172 {
173 return Err(zencodec::CodecError::new(
174 None,
175 zencodec::InvalidKind::Buffer.into(),
176 ));
177 }
178 for y in 0..rows.rows() {
179 pulse
180 .check()
181 .map_err(|r| zencodec::CodecError::new(None, r.into()))?;
182 rows.row_mut(y).copy_from_slice(self.0.row(first + y));
183 }
184 Ok(())
185 }
186}
187pub struct Options {
190 storage: StorageBudget,
191 max_output_bytes: u64,
192 batch_rows: NonZeroU32,
193 preset: PolicySet,
194 disk: Option<Disk>,
195 raw_disk: Option<planned::RawDisk>,
196}
197impl Options {
198 pub fn new(storage: StorageBudget, max_output_bytes: u64) -> Self {
201 Self {
202 storage,
203 max_output_bytes,
204 batch_rows: NonZeroU32::new(16).unwrap(),
205 preset: PolicySet::new(),
206 disk: None,
207 raw_disk: None,
208 }
209 }
210 pub fn with_disk_staging(
218 mut self,
219 mut file: File,
220 replay_bytes: NonZeroUsize,
221 ) -> io::Result<Self> {
222 let origin = file.stream_position()?;
223 if file.seek(SeekFrom::End(0))? != origin {
224 return Err(io::Error::new(
225 io::ErrorKind::InvalidInput,
226 "scratch must start at its end",
227 ));
228 }
229 self.disk = Some(Disk {
230 file,
231 origin,
232 replay_bytes,
233 });
234 Ok(self)
235 }
236 pub fn with_raw_disk_staging(mut self, mut file: File, max_bytes: u64) -> io::Result<Self> {
242 let origin = file.stream_position()?;
243 if file.seek(SeekFrom::End(0))? != origin {
244 return Err(io::Error::new(
245 io::ErrorKind::InvalidInput,
246 "raw scratch must start at its end",
247 ));
248 }
249 self.raw_disk = Some(planned::RawDisk {
250 file,
251 origin,
252 max_bytes,
253 });
254 Ok(self)
255 }
256 pub fn with_batch_rows(mut self, rows: NonZeroU32) -> Self {
259 self.batch_rows = rows;
260 self
261 }
262 #[deprecated(note = "Pass PolicySet or BoundPolicy as the operation policy argument")]
266 pub fn with_policy_set(mut self, preset: PolicySet) -> Self {
267 self.preset = preset;
268 self
269 }
270}
271#[derive(Clone, Copy, Debug, PartialEq, Eq)]
273#[non_exhaustive]
274pub enum Stage {
275 Prepare,
277 Policy,
279 Read,
281 Encode,
283 Finish,
285 Replay,
287 Write,
289 Patch,
291 Start,
293}
294#[derive(Clone, Copy, Debug, PartialEq, Eq)]
296#[non_exhaustive]
297pub enum Refusal {
298 Empty,
300 Overflow,
302 SourceChanged,
304 OutputLimit,
306 Patches,
308 DestinationChanged,
310 RequestOrder {
312 pass: u32,
314 first: u32,
316 },
317 PassLimit {
319 pass: u32,
321 },
322 IncompletePass {
324 pass: u32,
326 },
327 PullProtocol,
329 ReplayChanged {
331 pass: u32,
333 first: u32,
335 },
336 EarlyOutput {
338 pass: Option<u32>,
340 },
341 NoInputRoute,
343}
344impl fmt::Display for Refusal {
345 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
346 write!(f, "row encoding refused: {self:?}")
347 }
348}
349impl Error for Refusal {}
350
351#[derive(Clone, Copy, Debug, PartialEq, Eq)]
354pub struct ExactDepthContradiction {
355 pub first_row: u32,
358 pub claim: ExactDepth,
360}
361impl fmt::Display for ExactDepthContradiction {
362 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
363 write!(
364 f,
365 "row samples contradict exact-depth claim at row {}",
366 self.first_row
367 )
368 }
369}
370impl Error for ExactDepthContradiction {}
371type Cause = Box<dyn Error + Send + Sync>;
372pub struct Failure {
374 pub stage: Stage,
376 pub emitted: u64,
378 pub patched_bytes: u64,
380 pub report: Box<OperationReport>,
382 cause: Cause,
383}
384impl fmt::Debug for Failure {
385 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
386 f.debug_struct("RowEncodeFailure")
387 .field("stage", &self.stage)
388 .field("emitted", &self.emitted)
389 .field("patched_bytes", &self.patched_bytes)
390 .field("report", &self.report)
391 .finish_non_exhaustive()
392 }
393}
394impl fmt::Display for Failure {
395 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
396 write!(
397 f,
398 "row encoding failed during {:?} after {} appended bytes",
399 self.stage, self.emitted
400 )
401 }
402}
403impl Error for Failure {
404 fn source(&self) -> Option<&(dyn Error + 'static)> {
405 Some(self.cause.as_ref())
406 }
407}
408#[derive(Debug)]
410pub struct Completed {
411 pub emitted: u64,
413 pub patched_bytes: u64,
415 pub schedule: OutputSchedule,
417 pub report: OperationReport,
419}
420fn check(pulse: &dyn zencodec::Pulse) -> Result<(), Cause> {
421 pulse
422 .check()
423 .map_err(|r| At::wrap(zencodec::CodecError::new(None, r.into())).into())
424}
425fn validate_source<S: Source>(
426 source: &S,
427 desc: &Description,
428 mechanism: OutputSource,
429) -> Result<(), Cause> {
430 if source.description() != *desc || source.mechanism() != mechanism {
431 return Err(Refusal::SourceChanged.into());
432 }
433 Ok(())
434}
435fn count(total: &mut u64, len: usize, limit: u64) -> Result<(), Cause> {
436 let next = total.checked_add(len as u64).ok_or(Refusal::Overflow)?;
437 if next > limit {
438 return Err(Refusal::OutputLimit.into());
439 }
440 *total = next;
441 Ok(())
442}
443fn write_all(
444 writer: &mut impl Write,
445 mut bytes: &[u8],
446 emitted: &mut u64,
447 pulse: &dyn zencodec::Pulse,
448) -> Result<(), Cause> {
449 while !bytes.is_empty() {
450 check(pulse)?;
451 match writer.write(bytes) {
452 Ok(0) => return Err(io::Error::from(io::ErrorKind::WriteZero).into()),
453 Ok(n) if n <= bytes.len() => {
454 *emitted += n as u64;
455 bytes = &bytes[n..];
456 }
457 Ok(_) => {
458 return Err(io::Error::new(
459 io::ErrorKind::InvalidData,
460 "writer returned an invalid count",
461 )
462 .into());
463 }
464 Err(e) if e.kind() == io::ErrorKind::Interrupted => continue,
465 Err(e) => return Err(e.into()),
466 }
467 }
468 check(pulse)
469}
470struct Disk {
471 file: File,
472 origin: u64,
473 replay_bytes: NonZeroUsize,
474}
475impl Disk {
476 fn append(&mut self, bytes: &[u8], pulse: &dyn zencodec::Pulse) -> Result<(), Cause> {
477 let mut written = 0;
478 write_all(&mut self.file, bytes, &mut written, pulse)
479 }
480 fn replay(
481 &mut self,
482 total: u64,
483 storage: &StorageBudget,
484 writer: &mut impl Write,
485 emitted: &mut u64,
486 boundary: &mut Stage,
487 pulse: &dyn zencodec::Pulse,
488 ) -> Result<(), Cause> {
489 *boundary = Stage::Replay;
490 check(pulse)?;
491 self.file.flush()?;
492 self.file.seek(SeekFrom::Start(self.origin))?;
493 let size = total.min(self.replay_bytes.get() as u64) as usize;
494 let mut buffer = storage.bytes(size)?;
495 let mut remaining = total;
496 while remaining != 0 {
497 *boundary = Stage::Replay;
498 check(pulse)?;
499 let n = remaining.min(size as u64) as usize;
500 self.file.read_exact(&mut buffer.as_mut_slice()[..n])?;
501 check(pulse)?;
502 *boundary = Stage::Write;
503 write_all(writer, &buffer.as_slice()[..n], emitted, pulse)?;
504 remaining -= n as u64;
505 }
506 Ok(())
507 }
508}
509fn stage(
510 bytes: &[u8],
511 storage: &StorageBudget,
512 packets: &mut VecDeque<OwnedStorage<u8>>,
513) -> Result<(), Cause> {
514 if !bytes.is_empty() {
515 packets.try_reserve(1)?;
516 let mut owned = storage.bytes(bytes.len())?;
517 owned.as_mut_slice().copy_from_slice(bytes);
518 packets.push_back(owned);
519 }
520 Ok(())
521}
522pub fn encode<E: Encoder, S: Source, W: Write, R: OperationPolicy>(
538 encoder: E,
539 source: S,
540 writer: W,
541 resolver: R,
542 options: Options,
543 pulse: Option<&dyn zencodec::Pulse>,
544) -> Result<Completed, At<Failure>> {
545 encode_to_destination(encoder, source, Forward(writer), resolver, options, pulse)
546}
547
548pub fn encode_to_destination<E: Encoder, S: Source, D: Destination, R: OperationPolicy>(
554 encoder: E,
555 source: S,
556 writer: D,
557 resolver: R,
558 options: Options,
559 pulse: Option<&dyn zencodec::Pulse>,
560) -> Result<Completed, At<Failure>> {
561 run_rows(
562 LegacyStartup(encoder),
563 source,
564 writer,
565 resolver,
566 options,
567 pulse,
568 )
569}
570
571pub fn encode_job<J: EncodeJob, S: Source, W: Write, R: OperationPolicy>(
581 job: J,
582 source: S,
583 writer: W,
584 resolver: R,
585 options: Options,
586 pulse: Option<&dyn zencodec::Pulse>,
587) -> Result<Completed, At<Failure>>
588where
589 J::Enc: Encoder,
590{
591 encode_job_to_destination(job, source, Forward(writer), resolver, options, pulse)
592}
593
594pub fn encode_job_to_destination<J: EncodeJob, S: Source, D: Destination, R: OperationPolicy>(
606 job: J,
607 source: S,
608 writer: D,
609 resolver: R,
610 options: Options,
611 pulse: Option<&dyn zencodec::Pulse>,
612) -> Result<Completed, At<Failure>>
613where
614 J::Enc: Encoder,
615{
616 planned::run(
617 PlannedStartup(job),
618 source,
619 writer,
620 resolver,
621 options,
622 pulse,
623 )
624}
625
626pub fn encode_dyn_job<S: Source, W: Write, R: OperationPolicy>(
634 job: Box<dyn DynEncodeJob>,
635 source: S,
636 writer: W,
637 resolver: R,
638 options: Options,
639 pulse: Option<&dyn zencodec::Pulse>,
640) -> Result<Completed, At<Failure>> {
641 encode_dyn_job_to_destination(job, source, Forward(writer), resolver, options, pulse)
642}
643
644pub fn encode_dyn_job_to_destination<S: Source, D: Destination, R: OperationPolicy>(
650 job: Box<dyn DynEncodeJob>,
651 source: S,
652 writer: D,
653 resolver: R,
654 options: Options,
655 pulse: Option<&dyn zencodec::Pulse>,
656) -> Result<Completed, At<Failure>> {
657 planned::run(
658 DynPlannedStartup(job),
659 source,
660 writer,
661 resolver,
662 options,
663 pulse,
664 )
665}
666
667fn validate_row_plan(
668 plan: EncodePlan,
669 declaration: &EncodeDeclaration<'_>,
670) -> Result<EncodePlan, Cause> {
671 plan.validate(declaration)?;
672 if matches!(plan.input, InputRequirement::WholeRaster) {
673 return Err(EncodePlanError::InputProtocol.into());
674 }
675 Ok(plan)
676}
677
678trait RowStartup {
679 type Enc: Encoder;
680 const EARLY_BUFFER: bool;
682 fn plan(&mut self, declaration: &EncodeDeclaration<'_>) -> Result<EncodePlan, Cause>;
683 fn start(
684 self,
685 declaration: &EncodeDeclaration<'_>,
686 plan: &EncodePlan,
687 ) -> Result<Self::Enc, Cause>;
688}
689struct LegacyStartup<E>(E);
690impl<E: Encoder> RowStartup for LegacyStartup<E> {
691 type Enc = E;
692 const EARLY_BUFFER: bool = true;
693 fn plan(&mut self, declaration: &EncodeDeclaration<'_>) -> Result<EncodePlan, Cause> {
694 let timing = self.0.begin_output(
695 declaration.width,
696 declaration.height,
697 declaration.descriptor,
698 declaration.access,
699 )?;
700 Ok(EncodePlan::rows(timing))
701 }
702 fn start(self, _: &EncodeDeclaration<'_>, _: &EncodePlan) -> Result<E, Cause> {
703 Ok(self.0)
704 }
705}
706struct PlannedStartup<J>(J);
707impl<J: EncodeJob> RowStartup for PlannedStartup<J>
708where
709 J::Enc: Encoder,
710{
711 type Enc = J::Enc;
712 const EARLY_BUFFER: bool = false;
713 fn plan(&mut self, declaration: &EncodeDeclaration<'_>) -> Result<EncodePlan, Cause> {
714 validate_row_plan(self.0.plan_encode(declaration)?, declaration)
715 }
716 fn start(
717 self,
718 declaration: &EncodeDeclaration<'_>,
719 plan: &EncodePlan,
720 ) -> Result<Self::Enc, Cause> {
721 let mut encoder = self.0.encoder()?;
722 let observed = encoder.begin_declared(declaration)?;
723 plan.validate_started(&observed)?;
724 Ok(encoder)
725 }
726}
727
728struct DynPlannedStartup(Box<dyn DynEncodeJob>);
729impl RowStartup for DynPlannedStartup {
730 type Enc = DynRows;
731 const EARLY_BUFFER: bool = false;
732 fn plan(&mut self, declaration: &EncodeDeclaration<'_>) -> Result<EncodePlan, Cause> {
733 validate_row_plan(self.0.plan_encode(declaration)?, declaration)
734 }
735 fn start(
736 self,
737 declaration: &EncodeDeclaration<'_>,
738 plan: &EncodePlan,
739 ) -> Result<Self::Enc, Cause> {
740 let mut encoder = self.0.into_encoder()?;
741 let observed = encoder.begin_declared(declaration)?;
742 plan.validate_started(&observed)?;
743 Ok(DynRows(encoder))
744 }
745}
746
747struct DynRows(Box<dyn DynEncoder>);
750impl Encoder for DynRows {
751 type Error = zencodec::decode::ErasedError;
752 fn reject(operation: zencodec::UnsupportedOperation) -> Self::Error {
753 Self::Error::new(Box::new(operation))
754 }
755 fn encode(self, pixels: PixelSlice<'_>) -> Result<EncodeOutput, Self::Error> {
756 self.0.encode(pixels).map_err(Self::Error::new)
757 }
758 fn pending_output(&self) -> &[u8] {
759 self.0.pending_output()
760 }
761 fn consume_output(&mut self, n: usize) {
762 self.0.consume_output(n);
763 }
764 fn push_rows(&mut self, pixels: PixelSlice<'_>) -> Result<(), Self::Error> {
765 self.0.push_rows(pixels).map_err(Self::Error::new)
766 }
767 fn pull_rows(
768 &mut self,
769 rows: &mut dyn zencodec::encode::EncodeRows,
770 pulse: Option<&dyn zencodec::Pulse>,
771 ) -> Result<zencodec::encode::PullProgress, Self::Error> {
772 self.0.pull_rows(rows, pulse).map_err(Self::Error::new)
773 }
774 fn finish(self) -> Result<EncodeOutput, Self::Error> {
775 self.0.finish().map_err(Self::Error::new)
776 }
777 fn finish_output(
778 self,
779 ) -> Result<zencodec::output_patch::OutputFinish<EncodeOutput>, Self::Error> {
780 self.0.finish_output().map_err(Self::Error::new)
781 }
782}
783
784fn run_rows<P: RowStartup, S: Source, D: Destination, R: OperationPolicy>(
785 mut startup: P,
786 mut source: S,
787 mut writer: D,
788 mut resolver: R,
789 mut options: Options,
790 pulse: Option<&dyn zencodec::Pulse>,
791) -> Result<Completed, At<Failure>> {
792 let control = pulse.unwrap_or(&zencodec::NoPulse);
793 let mut boundary = Stage::Prepare;
794 let mut emitted = 0;
795 let mut patched_bytes = 0;
796 let mut report = OperationReport::default();
797 let bound_requirements = resolver.requirements();
798 let entry_requirements = options.preset;
799 let run = || -> Result<OutputSchedule, Cause> {
800 check(control)?;
801 boundary = Stage::Policy;
805 entry_requirements.check_workspace(None)?;
806 if let Some(required) = bound_requirements {
807 required.check_workspace(None)?;
808 }
809 boundary = Stage::Prepare;
810 let desc = source.description();
811 let mechanism = source.mechanism();
812 if desc.width == 0 || desc.height == 0 {
813 return Err(Refusal::Empty.into());
814 }
815 let stride = (desc.width as usize)
816 .checked_mul(desc.descriptor.bytes_per_pixel())
817 .ok_or(Refusal::Overflow)?;
818 let rows = options.batch_rows.get().min(desc.height);
819 let len = stride.checked_mul(rows as usize).ok_or(Refusal::Overflow)?;
820 let early_buffer = if P::EARLY_BUFFER {
821 Some(options.storage.bytes(len)?)
822 } else {
823 None
824 };
825 let access = if writer.can_patch() {
826 OutputAccess::SeekBack
827 } else {
828 OutputAccess::Forward
829 };
830 let mut declaration =
831 EncodeDeclaration::new(desc.width, desc.height, desc.descriptor).with_access(access);
832 if let Some(color) = &desc.color_context {
833 declaration = declaration.with_color(color);
834 }
835 if let Some(exact) = desc.exact_depth {
836 declaration = declaration.with_exact_depth(exact);
837 }
838 declaration.validate_exact_depth()?;
841 let plan = startup.plan(&declaration)?;
842 let timing = plan.timing;
843 if timing.patches_at_finish() && !writer.can_patch() {
844 return Err(Refusal::Patches.into());
845 }
846 check(control)?;
847 let mut finish_first = OutputScheduleOffer::new(OutputSchedule::FinishFirst);
848 finish_first.staging = Some(StagingFacts::new(
849 if options.disk.is_some() {
850 StagingKind::Disk
851 } else {
852 StagingKind::Memory
853 },
854 0,
855 if options.disk.is_some() {
856 options.max_output_bytes
857 } else {
858 options.storage.max_bytes() as u64
859 },
860 ));
861 let offers = [finish_first, OutputScheduleOffer::new(OutputSchedule::Live)];
862 let offers = &offers[..if timing.streams() { 2 } else { 1 }];
863 boundary = Stage::Policy;
864 let (index, record) = match resolve_output_schedule(
865 OutputScheduleFacts::new(timing, mechanism),
866 offers,
867 &mut resolver,
868 options.preset,
869 pulse,
870 ) {
871 Ok(selected) => selected,
872 Err(error) => {
873 report.record(error.error().record());
874 return Err(error.into());
875 }
876 };
877 report.record(record);
878 let schedule = offers[index].schedule;
879 check(control)?;
880 boundary = Stage::Read;
881 validate_source(&source, &desc, mechanism)?;
882 let current_access = if writer.can_patch() {
883 OutputAccess::SeekBack
884 } else {
885 OutputAccess::Forward
886 };
887 if current_access != access {
888 return Err(Refusal::DestinationChanged.into());
889 }
890 boundary = Stage::Prepare;
891 let mut buffer = match early_buffer {
892 Some(buffer) => buffer,
893 None => options.storage.bytes(len)?,
894 };
895 check(control)?;
896 boundary = Stage::Start;
897 let mut encoder = startup.start(&declaration, &plan)?;
898 drop(plan);
901 let mut staged = VecDeque::new();
902 let mut produced = 0;
903 let mut y = 0;
904 loop {
905 check(control)?;
906 boundary = Stage::Read;
907 validate_source(&source, &desc, mechanism)?;
908 if y == desc.height {
911 break;
912 }
913 boundary = Stage::Encode;
914 let pending = encoder.pending_output();
915 count(&mut produced, pending.len(), options.max_output_bytes)?;
916 if schedule == OutputSchedule::Live {
917 boundary = Stage::Write;
918 write_all(&mut writer, pending, &mut emitted, control)?;
919 } else {
920 boundary = Stage::Encode;
921 if let Some(disk) = &mut options.disk {
922 disk.origin.checked_add(produced).ok_or(Refusal::Overflow)?;
923 disk.append(pending, control)?;
924 } else {
925 stage(pending, &options.storage, &mut staged)?;
926 }
927 }
928 encoder.consume_output(pending.len());
929 let count = rows.min(desc.height - y);
930 let size = stride
931 .checked_mul(count as usize)
932 .ok_or(Refusal::Overflow)?;
933 boundary = Stage::Read;
934 validate_source(&source, &desc, mechanism)?;
935 let mut destination = PixelSliceMut::new(
936 &mut buffer.as_mut_slice()[..size],
937 desc.width,
938 count,
939 stride,
940 desc.descriptor,
941 )?;
942 if let Some(context) = &desc.color_context {
943 destination = destination.with_color_context(context.clone());
944 }
945 source.read_rows(y, destination, pulse)?;
946 check(control)?;
947 validate_source(&source, &desc, mechanism)?;
948 boundary = Stage::Encode;
949 let mut pixels = PixelSlice::new(
950 &buffer.as_slice()[..size],
951 desc.width,
952 count,
953 stride,
954 desc.descriptor,
955 )?;
956 if let Some(exact) = desc.exact_depth {
957 for row in 0..pixels.rows() {
961 check(control)?;
962 if !exact.holds_for(&pixels.sub_rows(row, 1))? {
963 return Err(ExactDepthContradiction {
964 first_row: y + row,
965 claim: exact,
966 }
967 .into());
968 }
969 }
970 boundary = Stage::Read;
973 validate_source(&source, &desc, mechanism)?;
974 boundary = Stage::Encode;
975 pixels = pixels.with_exact_depth(exact)?;
976 }
977 if let Some(context) = &desc.color_context {
980 pixels = pixels.with_color_context(context.clone());
981 }
982 encoder.push_rows(pixels)?;
983 y += count;
984 }
985 drop(source);
986 drop(buffer);
987 drop(desc);
988 boundary = Stage::Finish;
989 check(control)?;
990 let finished = encoder.finish_output()?;
991 check(control)?;
992 finished.validate(produced)?;
993 if !finished.patches.is_empty() && (!writer.can_patch() || !timing.patches_at_finish()) {
994 return Err(Refusal::Patches.into());
995 }
996 let patches = finished.patches;
997 let rest = finished.tail;
998 count(&mut produced, rest.len(), options.max_output_bytes)?;
999 if schedule == OutputSchedule::FinishFirst {
1000 if let Some(disk) = &mut options.disk {
1001 disk.origin.checked_add(produced).ok_or(Refusal::Overflow)?;
1002 disk.append(rest.data(), control)?;
1003 drop(rest);
1004 disk.replay(
1005 produced,
1006 &options.storage,
1007 &mut writer,
1008 &mut emitted,
1009 &mut boundary,
1010 control,
1011 )?;
1012 } else {
1013 stage(rest.data(), &options.storage, &mut staged)?;
1014 drop(rest);
1015 boundary = Stage::Write;
1016 while let Some(bytes) = staged.pop_front() {
1017 write_all(&mut writer, bytes.as_slice(), &mut emitted, control)?;
1018 }
1019 }
1020 } else {
1021 boundary = Stage::Write;
1022 write_all(&mut writer, rest.data(), &mut emitted, control)?;
1023 }
1024 check(control)?;
1025 if !patches.is_empty() {
1026 boundary = Stage::Patch;
1027 for patch in patches {
1028 check(control)?;
1029 writer.position(patch.offset)?;
1030 write_all(&mut writer, &patch.bytes, &mut patched_bytes, control)?;
1031 }
1032 check(control)?;
1033 writer.position(produced)?;
1034 }
1035 boundary = Stage::Write;
1036 check(control)?;
1037 writer.flush()?;
1038 check(control)?;
1039 Ok(schedule)
1040 };
1041 match run() {
1042 Ok(schedule) => Ok(Completed {
1043 emitted,
1044 patched_bytes,
1045 schedule,
1046 report,
1047 }),
1048 Err(cause) => Err(At::wrap(Failure {
1049 stage: boundary,
1050 emitted,
1051 patched_bytes,
1052 report: Box::new(report),
1053 cause,
1054 })),
1055 }
1056}
1057
1058#[cfg(test)]
1059mod tests {
1060 use super::*;
1061 #[test]
1062 fn raster_honors_both_strides_and_refuses_context_mismatch_before_copy() {
1063 let input = [1, 2, 3, 99, 98, 97, 4, 5, 6];
1064 let context = Arc::new(ColorContext::from_cicp(zencodec::Cicp::SRGB));
1065 let pixels = PixelSlice::new(&input, 1, 2, 6, PixelDescriptor::RGB8_SRGB)
1066 .unwrap()
1067 .with_color_context(context.clone());
1068 let mut source = Raster::new(pixels);
1069 let mut output = [77; 12];
1070 let view = PixelSliceMut::new(&mut output, 1, 2, 9, PixelDescriptor::RGB8_SRGB).unwrap();
1071 assert!(
1072 source
1073 .read_rows(0, view, Some(&zencodec::how_far::NoPulse))
1074 .is_err()
1075 );
1076 assert_eq!(output, [77; 12]);
1077 let view = PixelSliceMut::new(&mut output, 1, 2, 9, PixelDescriptor::RGB8_SRGB)
1078 .unwrap()
1079 .with_color_context(context);
1080 source
1081 .read_rows(0, view, Some(&zencodec::how_far::NoPulse))
1082 .unwrap();
1083 assert_eq!(output, [1, 2, 3, 77, 77, 77, 77, 77, 77, 4, 5, 6]);
1084 }
1085}
1086
1087#[cfg(test)]
1088mod patch_tests {
1089 use super::*;
1090 use crate::destination::SeekBack;
1091 use zencodec::{
1092 encode::{EncodeOutput, OutputTiming},
1093 output_patch::{OutputFinish, Patch, PatchError},
1094 };
1095 struct Patching {
1096 pending: bool,
1097 bad: bool,
1098 }
1099 impl Encoder for Patching {
1100 type Error = io::Error;
1101 fn reject(op: zencodec::UnsupportedOperation) -> Self::Error {
1102 io::Error::other(op)
1103 }
1104 fn encode(self, _: PixelSlice<'_>) -> Result<EncodeOutput, Self::Error> {
1105 Err(io::Error::other("negotiated path only"))
1106 }
1107 fn begin_output(
1108 &mut self,
1109 _: u32,
1110 _: u32,
1111 _: PixelDescriptor,
1112 _: OutputAccess,
1113 ) -> Result<OutputTiming, Self::Error> {
1114 self.pending = true;
1115 Ok(OutputTiming::streaming(0).with_patches())
1116 }
1117 fn pending_output(&self) -> &[u8] {
1118 if self.pending { &[0, 0] } else { &[] }
1119 }
1120 fn consume_output(&mut self, _: usize) {
1121 self.pending = false;
1122 }
1123 fn push_rows(&mut self, _: PixelSlice<'_>) -> Result<(), Self::Error> {
1124 Ok(())
1125 }
1126 fn finish_output(self) -> Result<OutputFinish<EncodeOutput>, Self::Error> {
1127 Ok(OutputFinish::new(
1128 EncodeOutput::new(alloc::vec![3], zencodec::ImageFormat::Unknown),
1129 alloc::vec![Patch {
1130 offset: if self.bad { u64::MAX } else { 0 },
1131 bytes: alloc::vec![1, 2],
1132 }],
1133 ))
1134 }
1135 }
1136 struct Short {
1137 cursor: io::Cursor<alloc::vec::Vec<u8>>,
1138 patch: bool,
1139 fail: bool,
1140 }
1141 impl Write for Short {
1142 fn write(&mut self, bytes: &[u8]) -> io::Result<usize> {
1143 if self.patch && self.fail && self.cursor.position() == 7 {
1144 return Err(io::ErrorKind::BrokenPipe.into());
1145 }
1146 self.cursor.write(&bytes[..bytes.len().min(1)])
1147 }
1148 fn flush(&mut self) -> io::Result<()> {
1149 Ok(())
1150 }
1151 }
1152 impl Seek for Short {
1153 fn seek(&mut self, from: SeekFrom) -> io::Result<u64> {
1154 if matches!(from, SeekFrom::Start(6)) {
1155 self.patch = true;
1156 }
1157 self.cursor.seek(from)
1158 }
1159 }
1160 fn source() -> Raster<'static> {
1161 Raster::new(PixelSlice::new(&[7, 8, 9], 1, 1, 3, PixelDescriptor::RGB8_SRGB).unwrap())
1162 }
1163 fn options() -> Options {
1164 Options::new(StorageBudget::new(1024, 16), 1024)
1165 }
1166 #[test]
1167 fn cancellation_during_patch_or_restore_prevents_later_effects() {
1168 use core::sync::atomic::{AtomicBool, Ordering::Relaxed};
1169 use zencodec::how_far::{Child, Execution, PhaseSpec, PlanError, Report};
1170 struct Cancel(AtomicBool);
1171 impl zencodec::enough::Stop for Cancel {
1172 fn check(&self) -> Result<(), zencodec::enough::StopReason> {
1173 if self.0.load(Relaxed) {
1174 Err(zencodec::enough::StopReason::Cancelled)
1175 } else {
1176 Ok(())
1177 }
1178 }
1179 }
1180 impl Report for Cancel {
1181 fn advance(&self, _: u64) {}
1182 }
1183 impl zencodec::Pulse for Cancel {
1184 fn split(
1185 &self,
1186 _: Execution,
1187 _: &[PhaseSpec<'_>],
1188 ) -> Result<alloc::vec::Vec<Child<'_>>, PlanError> {
1189 Err(PlanError::Unsupported)
1190 }
1191 }
1192 struct Writer<'a> {
1193 cursor: io::Cursor<alloc::vec::Vec<u8>>,
1194 pulse: &'a Cancel,
1195 positions: usize,
1196 flushes: usize,
1197 cancel_on_patch: bool,
1198 cancel_on_restore: bool,
1199 }
1200 impl Write for Writer<'_> {
1201 fn write(&mut self, bytes: &[u8]) -> io::Result<usize> {
1202 let n = self.cursor.write(bytes)?;
1203 if self.positions == 2 && self.cancel_on_patch {
1204 self.pulse.0.store(true, Relaxed);
1205 }
1206 Ok(n)
1207 }
1208 fn flush(&mut self) -> io::Result<()> {
1209 self.flushes += 1;
1210 Ok(())
1211 }
1212 }
1213 impl Seek for Writer<'_> {
1214 fn seek(&mut self, at: SeekFrom) -> io::Result<u64> {
1215 self.positions += 1;
1216 let pos = self.cursor.seek(at)?;
1217 if self.positions == 3 && self.cancel_on_restore {
1218 self.pulse.0.store(true, Relaxed);
1219 }
1220 Ok(pos)
1221 }
1222 }
1223 for schedule in [OutputSchedule::FinishFirst, OutputSchedule::Live] {
1224 for mode in 0..3 {
1225 let pulse = Cancel(AtomicBool::new(false));
1226 let mut writer = Writer {
1227 cursor: io::Cursor::new(alloc::vec::Vec::new()),
1228 pulse: &pulse,
1229 positions: 0,
1230 flushes: 0,
1231 cancel_on_patch: mode == 0,
1232 cancel_on_restore: mode == 1,
1233 };
1234 let result = encode_to_destination(
1235 Patching {
1236 pending: false,
1237 bad: false,
1238 },
1239 source(),
1240 SeekBack::new(&mut writer).unwrap(),
1241 PolicySet::new().require_output_schedule(schedule),
1242 options(),
1243 Some(&pulse),
1244 );
1245 assert_eq!(writer.cursor.get_ref(), &[1, 2, 3]);
1246 if mode == 2 {
1247 let completed = result.unwrap();
1248 assert_eq!((completed.emitted, completed.patched_bytes), (3, 2));
1249 assert_eq!((writer.positions, writer.flushes), (3, 1));
1250 } else {
1251 let error = result.unwrap_err();
1252 assert_eq!(
1253 zencodec::find_cause::<zencodec::CodecError>(&error)
1254 .unwrap()
1255 .category(),
1256 zencodec::ErrorCategory::Stopped(zencodec::enough::StopReason::Cancelled)
1257 );
1258 assert_eq!((error.error().emitted, error.error().patched_bytes), (3, 2));
1259 assert_eq!(writer.flushes, 0);
1260 assert_eq!(writer.positions, if mode == 0 { 2 } else { 3 });
1261 }
1262 }
1263 }
1264 }
1265 #[test]
1266 fn incompatible_destination_refuses_before_policy_or_any_output() {
1267 let mut bytes = alloc::vec::Vec::new();
1268 let error =
1269 encode(
1270 Patching {
1271 pending: false,
1272 bad: false,
1273 },
1274 source(),
1275 &mut bytes,
1276 |_: zencodec::operation::Question<'_>| -> Result<
1277 zencodec::operation::Decision,
1278 zencodec::At<core::convert::Infallible>,
1279 > { panic!("must refuse before policy") },
1280 options(),
1281 Some(&zencodec::how_far::NoPulse),
1282 )
1283 .unwrap_err();
1284 assert!(bytes.is_empty());
1285 assert_eq!(
1286 zencodec::find_cause::<Refusal>(&error),
1287 Some(&Refusal::Patches)
1288 );
1289 assert_eq!(error.error().emitted, 0);
1290 }
1291 #[test]
1292 fn patches_preflight_all_ranges_and_report_partial_overwrites_separately() {
1293 for schedule in [OutputSchedule::FinishFirst, OutputSchedule::Live] {
1294 for (bad, fail) in [(false, false), (true, false), (false, true)] {
1295 let mut writer = Short {
1296 cursor: io::Cursor::new(b"prefix".to_vec()),
1297 patch: false,
1298 fail,
1299 };
1300 writer.cursor.set_position(6);
1301 let result = encode_to_destination(
1302 Patching {
1303 pending: false,
1304 bad,
1305 },
1306 source(),
1307 SeekBack::new(&mut writer).unwrap(),
1308 PolicySet::new().require_output_schedule(schedule),
1309 options(),
1310 Some(&zencodec::how_far::NoPulse),
1311 );
1312 assert_eq!(&writer.cursor.get_ref()[..6], b"prefix");
1313 if bad {
1314 let error = result.unwrap_err();
1315 assert_eq!(
1316 zencodec::find_cause::<PatchError>(&error),
1317 Some(&PatchError::OutOfRange)
1318 );
1319 let prior = if schedule == OutputSchedule::Live {
1320 2
1321 } else {
1322 0
1323 };
1324 assert_eq!(error.error().emitted, prior);
1325 assert_eq!(writer.cursor.get_ref().len(), 6 + prior as usize);
1326 assert_eq!(error.error().patched_bytes, 0);
1327 } else if fail {
1328 let error = result.unwrap_err();
1329 assert_eq!(error.error().stage, Stage::Patch);
1330 assert_eq!(error.error().emitted, 3);
1331 assert_eq!(error.error().patched_bytes, 1);
1332 assert_eq!(
1333 zencodec::find_cause::<io::Error>(&error).unwrap().kind(),
1334 io::ErrorKind::BrokenPipe
1335 );
1336 assert_eq!(&writer.cursor.get_ref()[6..], &[1, 0, 3]);
1337 } else {
1338 let completed = result.unwrap();
1339 assert_eq!(completed.emitted, 3);
1340 assert_eq!(completed.patched_bytes, 2);
1341 assert_eq!(writer.cursor.position(), 9);
1342 assert_eq!(&writer.cursor.get_ref()[6..], &[1, 2, 3]);
1343 }
1344 }
1345 }
1346 }
1347}