Coverage Report

Created: 2026-10-17 08:02

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/rust/registry/src/index.crates.io-1949cf8c6b5b557f/exr-1.74.2/src/block/writer.rs
Line
Count
Source
1
//! Composable structures to handle writing an image.
2
3
use std::{fmt::Debug, io::Seek, iter::Peekable, ops::Not};
4
5
use smallvec::alloc::collections::BTreeMap;
6
7
use crate::{
8
    block::{chunk::Chunk, UncompressedBlock},
9
    error::{usize_to_u64, Error, Result, UnitResult},
10
    io::{Data, Tracking, Write},
11
    meta::{attribute::LineOrder, Headers, MetaData, OffsetTables},
12
};
13
14
/// Write an exr file by writing one chunk after another in a closure.
15
/// In the closure, you are provided a chunk writer, which should be used to
16
/// write all the chunks. Assumes the your write destination is buffered.
17
78
pub fn write_chunks_with<W: Write + Seek>(
18
78
    buffered_write: W,
19
78
    headers: Headers,
20
78
    pedantic: bool,
21
78
    write_chunks: impl FnOnce(MetaData, &mut ChunkWriter<W>) -> UnitResult,
22
78
) -> UnitResult {
23
    // this closure approach ensures that after writing all chunks, the file is
24
    // always completed and checked and flushed
25
78
    let (meta, mut writer) = ChunkWriter::new_for_buffered(buffered_write, headers, pedantic)?;
26
78
    write_chunks(meta, &mut writer)?;
27
78
    writer.complete_meta_data()
28
78
}
Unexecuted instantiation: exr::block::writer::write_chunks_with::<_, _>
Unexecuted instantiation: exr::block::writer::write_chunks_with::<&mut &mut std::io::cursor::Cursor<alloc::vec::Vec<u8>>, <exr::image::write::WriteImageWithOptions<exr::image::Layer<exr::image::SpecificChannels<image::codecs::openexr::write_buffer<&mut std::io::cursor::Cursor<alloc::vec::Vec<u8>>>::{closure#0}, (exr::meta::attribute::ChannelDescription, exr::meta::attribute::ChannelDescription, exr::meta::attribute::ChannelDescription)>>, fn(f64)>>::to_buffered<&mut &mut std::io::cursor::Cursor<alloc::vec::Vec<u8>>>::{closure#0}>
Unexecuted instantiation: exr::block::writer::write_chunks_with::<&mut &mut std::io::cursor::Cursor<alloc::vec::Vec<u8>>, <exr::image::write::WriteImageWithOptions<exr::image::Layer<exr::image::SpecificChannels<image::codecs::openexr::write_buffer<&mut std::io::cursor::Cursor<alloc::vec::Vec<u8>>>::{closure#1}, (exr::meta::attribute::ChannelDescription, exr::meta::attribute::ChannelDescription, exr::meta::attribute::ChannelDescription, exr::meta::attribute::ChannelDescription)>>, fn(f64)>>::to_buffered<&mut &mut std::io::cursor::Cursor<alloc::vec::Vec<u8>>>::{closure#0}>
Unexecuted instantiation: exr::block::writer::write_chunks_with::<&mut std::io::cursor::Cursor<&mut alloc::vec::Vec<u8>>, <exr::image::write::WriteImageWithOptions<exr::image::Layer<exr::image::SpecificChannels<image::codecs::openexr::write_buffer<std::io::cursor::Cursor<&mut alloc::vec::Vec<u8>>>::{closure#0}, (exr::meta::attribute::ChannelDescription, exr::meta::attribute::ChannelDescription, exr::meta::attribute::ChannelDescription)>>, fn(f64)>>::to_buffered<&mut std::io::cursor::Cursor<&mut alloc::vec::Vec<u8>>>::{closure#0}>
exr::block::writer::write_chunks_with::<&mut std::io::cursor::Cursor<&mut alloc::vec::Vec<u8>>, <exr::image::write::WriteImageWithOptions<exr::image::Layer<exr::image::SpecificChannels<image::codecs::openexr::write_buffer<std::io::cursor::Cursor<&mut alloc::vec::Vec<u8>>>::{closure#1}, (exr::meta::attribute::ChannelDescription, exr::meta::attribute::ChannelDescription, exr::meta::attribute::ChannelDescription, exr::meta::attribute::ChannelDescription)>>, fn(f64)>>::to_buffered<&mut std::io::cursor::Cursor<&mut alloc::vec::Vec<u8>>>::{closure#0}>
Line
Count
Source
17
78
pub fn write_chunks_with<W: Write + Seek>(
18
78
    buffered_write: W,
19
78
    headers: Headers,
20
78
    pedantic: bool,
21
78
    write_chunks: impl FnOnce(MetaData, &mut ChunkWriter<W>) -> UnitResult,
22
78
) -> UnitResult {
23
    // this closure approach ensures that after writing all chunks, the file is
24
    // always completed and checked and flushed
25
78
    let (meta, mut writer) = ChunkWriter::new_for_buffered(buffered_write, headers, pedantic)?;
26
78
    write_chunks(meta, &mut writer)?;
27
78
    writer.complete_meta_data()
28
78
}
29
30
/// Can consume compressed pixel chunks, writing them a file.
31
/// Use `sequential_blocks_compressor` or `parallel_blocks_compressor` to
32
/// compress your data, or use `compress_all_blocks_sequential` or
33
/// `compress_all_blocks_parallel`. Use `on_progress` to obtain a new writer
34
/// that triggers a callback for each block.
35
// #[must_use]
36
#[derive(Debug)]
37
#[must_use]
38
pub struct ChunkWriter<W> {
39
    header_count: usize,
40
    byte_writer: Tracking<W>,
41
    chunk_indices_byte_location: std::ops::Range<usize>,
42
    chunk_indices_increasing_y: OffsetTables,
43
    chunk_count: usize, // TODO compose?
44
}
45
46
/// A new writer that triggers a callback
47
/// for each block written to the inner writer.
48
#[derive(Debug)]
49
#[must_use]
50
pub struct OnProgressChunkWriter<'w, W, F> {
51
    chunk_writer: &'w mut W,
52
    written_chunks: usize,
53
    on_progress: F,
54
}
55
56
/// Write chunks to a byte destination.
57
/// Then write each chunk with `writer.write_chunk(chunk)`.
58
pub trait ChunksWriter: Sized {
59
    /// The total number of chunks that the complete file will contain.
60
    fn total_chunks_count(&self) -> usize;
61
62
    /// Any more calls will result in an error and have no effect.
63
    /// If writing results in an error, the file and the writer
64
    /// may remain in an invalid state and should not be used further.
65
    /// Errors when the chunk at this index was already written.
66
    fn write_chunk(&mut self, index_in_header_increasing_y: usize, chunk: Chunk) -> UnitResult;
67
68
    /// Obtain a new writer that calls the specified closure for each block that
69
    /// is written to this writer.
70
78
    fn on_progress<F>(&mut self, on_progress: F) -> OnProgressChunkWriter<'_, Self, F>
71
78
    where
72
78
        F: FnMut(f64),
73
    {
74
78
        OnProgressChunkWriter {
75
78
            chunk_writer: self,
76
78
            written_chunks: 0,
77
78
            on_progress,
78
78
        }
79
78
    }
Unexecuted instantiation: <_ as exr::block::writer::ChunksWriter>::on_progress::<_>
Unexecuted instantiation: <exr::block::writer::ChunkWriter<&mut &mut std::io::cursor::Cursor<alloc::vec::Vec<u8>>> as exr::block::writer::ChunksWriter>::on_progress::<fn(f64)>
<exr::block::writer::ChunkWriter<&mut std::io::cursor::Cursor<&mut alloc::vec::Vec<u8>>> as exr::block::writer::ChunksWriter>::on_progress::<fn(f64)>
Line
Count
Source
70
78
    fn on_progress<F>(&mut self, on_progress: F) -> OnProgressChunkWriter<'_, Self, F>
71
78
    where
72
78
        F: FnMut(f64),
73
    {
74
78
        OnProgressChunkWriter {
75
78
            chunk_writer: self,
76
78
            written_chunks: 0,
77
78
            on_progress,
78
78
        }
79
78
    }
80
81
    /// Obtain a new writer that can compress blocks to chunks, which are then
82
    /// passed to this writer.
83
0
    fn sequential_blocks_compressor<'w>(
84
0
        &'w mut self,
85
0
        meta: &'w MetaData,
86
0
    ) -> SequentialBlocksCompressor<'w, Self> {
87
0
        SequentialBlocksCompressor::new(meta, self)
88
0
    }
Unexecuted instantiation: <_ as exr::block::writer::ChunksWriter>::sequential_blocks_compressor
Unexecuted instantiation: <exr::block::writer::OnProgressChunkWriter<exr::block::writer::ChunkWriter<&mut &mut std::io::cursor::Cursor<alloc::vec::Vec<u8>>>, fn(f64)> as exr::block::writer::ChunksWriter>::sequential_blocks_compressor
Unexecuted instantiation: <exr::block::writer::OnProgressChunkWriter<exr::block::writer::ChunkWriter<&mut std::io::cursor::Cursor<&mut alloc::vec::Vec<u8>>>, fn(f64)> as exr::block::writer::ChunksWriter>::sequential_blocks_compressor
89
90
    #[cfg(feature = "rayon")]
91
    /// Obtain a new writer that can compress blocks to chunks on multiple
92
    /// threads, which are then passed to this writer. Returns none if the
93
    /// sequential compressor should be used instead (thread pool creation
94
    /// failure or too large performance overhead).
95
78
    fn parallel_blocks_compressor<'w>(
96
78
        &'w mut self,
97
78
        meta: &'w MetaData,
98
78
    ) -> Option<ParallelBlocksCompressor<'w, Self>> {
99
78
        ParallelBlocksCompressor::new(meta, self)
100
78
    }
Unexecuted instantiation: <_ as exr::block::writer::ChunksWriter>::parallel_blocks_compressor
Unexecuted instantiation: <exr::block::writer::OnProgressChunkWriter<exr::block::writer::ChunkWriter<&mut &mut std::io::cursor::Cursor<alloc::vec::Vec<u8>>>, fn(f64)> as exr::block::writer::ChunksWriter>::parallel_blocks_compressor
<exr::block::writer::OnProgressChunkWriter<exr::block::writer::ChunkWriter<&mut std::io::cursor::Cursor<&mut alloc::vec::Vec<u8>>>, fn(f64)> as exr::block::writer::ChunksWriter>::parallel_blocks_compressor
Line
Count
Source
95
78
    fn parallel_blocks_compressor<'w>(
96
78
        &'w mut self,
97
78
        meta: &'w MetaData,
98
78
    ) -> Option<ParallelBlocksCompressor<'w, Self>> {
99
78
        ParallelBlocksCompressor::new(meta, self)
100
78
    }
101
102
    /// Compresses all blocks to the file.
103
    /// The index of the block must be in increasing line order within the
104
    /// header. Obtain iterator with `MetaData::collect_ordered_blocks(...)`
105
    /// or similar methods.
106
0
    fn compress_all_blocks_sequential(
107
0
        mut self,
108
0
        meta: &MetaData,
109
0
        blocks: impl Iterator<Item = (usize, UncompressedBlock)>,
110
0
    ) -> UnitResult {
111
0
        let mut writer = self.sequential_blocks_compressor(meta);
112
113
        // TODO check block order if line order is not unspecified!
114
0
        for (index_in_header_increasing_y, block) in blocks {
115
0
            writer.compress_block(index_in_header_increasing_y, block)?;
116
        }
117
118
        // TODO debug_assert_eq!(self.is_complete());
119
0
        Ok(())
120
0
    }
Unexecuted instantiation: <_ as exr::block::writer::ChunksWriter>::compress_all_blocks_sequential::<_>
Unexecuted instantiation: <exr::block::writer::OnProgressChunkWriter<exr::block::writer::ChunkWriter<&mut &mut std::io::cursor::Cursor<alloc::vec::Vec<u8>>>, fn(f64)> as exr::block::writer::ChunksWriter>::compress_all_blocks_sequential::<core::iter::adapters::map::Map<core::iter::adapters::flatten::FlatMap<core::iter::adapters::enumerate::Enumerate<core::slice::iter::Iter<exr::meta::header::Header>>, core::iter::adapters::map::Map<alloc::boxed::Box<dyn core::iter::traits::iterator::Iterator<Item = (usize, exr::meta::TileIndices)> + core::marker::Send>, exr::block::enumerate_ordered_header_block_indices::{closure#0}::{closure#0}>, exr::block::enumerate_ordered_header_block_indices::{closure#0}>, <exr::meta::MetaData>::collect_ordered_blocks<<exr::meta::MetaData>::collect_ordered_block_data<<exr::image::write::WriteImageWithOptions<exr::image::Layer<exr::image::SpecificChannels<image::codecs::openexr::write_buffer<&mut std::io::cursor::Cursor<alloc::vec::Vec<u8>>>::{closure#0}, (exr::meta::attribute::ChannelDescription, exr::meta::attribute::ChannelDescription, exr::meta::attribute::ChannelDescription)>>, fn(f64)>>::to_buffered<&mut &mut std::io::cursor::Cursor<alloc::vec::Vec<u8>>>::{closure#0}::{closure#0}>::{closure#0}>::{closure#0}>>
Unexecuted instantiation: <exr::block::writer::OnProgressChunkWriter<exr::block::writer::ChunkWriter<&mut &mut std::io::cursor::Cursor<alloc::vec::Vec<u8>>>, fn(f64)> as exr::block::writer::ChunksWriter>::compress_all_blocks_sequential::<core::iter::adapters::map::Map<core::iter::adapters::flatten::FlatMap<core::iter::adapters::enumerate::Enumerate<core::slice::iter::Iter<exr::meta::header::Header>>, core::iter::adapters::map::Map<alloc::boxed::Box<dyn core::iter::traits::iterator::Iterator<Item = (usize, exr::meta::TileIndices)> + core::marker::Send>, exr::block::enumerate_ordered_header_block_indices::{closure#0}::{closure#0}>, exr::block::enumerate_ordered_header_block_indices::{closure#0}>, <exr::meta::MetaData>::collect_ordered_blocks<<exr::meta::MetaData>::collect_ordered_block_data<<exr::image::write::WriteImageWithOptions<exr::image::Layer<exr::image::SpecificChannels<image::codecs::openexr::write_buffer<&mut std::io::cursor::Cursor<alloc::vec::Vec<u8>>>::{closure#1}, (exr::meta::attribute::ChannelDescription, exr::meta::attribute::ChannelDescription, exr::meta::attribute::ChannelDescription, exr::meta::attribute::ChannelDescription)>>, fn(f64)>>::to_buffered<&mut &mut std::io::cursor::Cursor<alloc::vec::Vec<u8>>>::{closure#0}::{closure#0}>::{closure#0}>::{closure#0}>>
Unexecuted instantiation: <exr::block::writer::OnProgressChunkWriter<exr::block::writer::ChunkWriter<&mut std::io::cursor::Cursor<&mut alloc::vec::Vec<u8>>>, fn(f64)> as exr::block::writer::ChunksWriter>::compress_all_blocks_sequential::<core::iter::adapters::map::Map<core::iter::adapters::flatten::FlatMap<core::iter::adapters::enumerate::Enumerate<core::slice::iter::Iter<exr::meta::header::Header>>, core::iter::adapters::map::Map<alloc::boxed::Box<dyn core::iter::traits::iterator::Iterator<Item = (usize, exr::meta::TileIndices)> + core::marker::Send>, exr::block::enumerate_ordered_header_block_indices::{closure#0}::{closure#0}>, exr::block::enumerate_ordered_header_block_indices::{closure#0}>, <exr::meta::MetaData>::collect_ordered_blocks<<exr::meta::MetaData>::collect_ordered_block_data<<exr::image::write::WriteImageWithOptions<exr::image::Layer<exr::image::SpecificChannels<image::codecs::openexr::write_buffer<std::io::cursor::Cursor<&mut alloc::vec::Vec<u8>>>::{closure#0}, (exr::meta::attribute::ChannelDescription, exr::meta::attribute::ChannelDescription, exr::meta::attribute::ChannelDescription)>>, fn(f64)>>::to_buffered<&mut std::io::cursor::Cursor<&mut alloc::vec::Vec<u8>>>::{closure#0}::{closure#0}>::{closure#0}>::{closure#0}>>
Unexecuted instantiation: <exr::block::writer::OnProgressChunkWriter<exr::block::writer::ChunkWriter<&mut std::io::cursor::Cursor<&mut alloc::vec::Vec<u8>>>, fn(f64)> as exr::block::writer::ChunksWriter>::compress_all_blocks_sequential::<core::iter::adapters::map::Map<core::iter::adapters::flatten::FlatMap<core::iter::adapters::enumerate::Enumerate<core::slice::iter::Iter<exr::meta::header::Header>>, core::iter::adapters::map::Map<alloc::boxed::Box<dyn core::iter::traits::iterator::Iterator<Item = (usize, exr::meta::TileIndices)> + core::marker::Send>, exr::block::enumerate_ordered_header_block_indices::{closure#0}::{closure#0}>, exr::block::enumerate_ordered_header_block_indices::{closure#0}>, <exr::meta::MetaData>::collect_ordered_blocks<<exr::meta::MetaData>::collect_ordered_block_data<<exr::image::write::WriteImageWithOptions<exr::image::Layer<exr::image::SpecificChannels<image::codecs::openexr::write_buffer<std::io::cursor::Cursor<&mut alloc::vec::Vec<u8>>>::{closure#1}, (exr::meta::attribute::ChannelDescription, exr::meta::attribute::ChannelDescription, exr::meta::attribute::ChannelDescription, exr::meta::attribute::ChannelDescription)>>, fn(f64)>>::to_buffered<&mut std::io::cursor::Cursor<&mut alloc::vec::Vec<u8>>>::{closure#0}::{closure#0}>::{closure#0}>::{closure#0}>>
121
122
    #[cfg(feature = "rayon")]
123
    /// Compresses all blocks to the file.
124
    /// The index of the block must be in increasing line order within the
125
    /// header. Obtain iterator with `MetaData::collect_ordered_blocks(...)`
126
    /// or similar methods. Will fallback to sequential processing where
127
    /// threads are not available, or where it would not speed up the process.
128
78
    fn compress_all_blocks_parallel(
129
78
        mut self,
130
78
        meta: &MetaData,
131
78
        blocks: impl Iterator<Item = (usize, UncompressedBlock)>,
132
78
    ) -> UnitResult {
133
78
        let mut parallel_writer = match self.parallel_blocks_compressor(meta) {
134
0
            None => return self.compress_all_blocks_sequential(meta, blocks),
135
78
            Some(writer) => writer,
136
        };
137
138
        // TODO check block order if line order is not unspecified!
139
722k
        for (index_in_header_increasing_y, block) in blocks {
140
722k
            parallel_writer.add_block_to_compression_queue(index_in_header_increasing_y, block)?;
141
        }
142
143
        // TODO debug_assert_eq!(self.is_complete());
144
78
        Ok(())
145
78
    }
Unexecuted instantiation: <_ as exr::block::writer::ChunksWriter>::compress_all_blocks_parallel::<_>
Unexecuted instantiation: <exr::block::writer::OnProgressChunkWriter<exr::block::writer::ChunkWriter<&mut &mut std::io::cursor::Cursor<alloc::vec::Vec<u8>>>, fn(f64)> as exr::block::writer::ChunksWriter>::compress_all_blocks_parallel::<core::iter::adapters::map::Map<core::iter::adapters::flatten::FlatMap<core::iter::adapters::enumerate::Enumerate<core::slice::iter::Iter<exr::meta::header::Header>>, core::iter::adapters::map::Map<alloc::boxed::Box<dyn core::iter::traits::iterator::Iterator<Item = (usize, exr::meta::TileIndices)> + core::marker::Send>, exr::block::enumerate_ordered_header_block_indices::{closure#0}::{closure#0}>, exr::block::enumerate_ordered_header_block_indices::{closure#0}>, <exr::meta::MetaData>::collect_ordered_blocks<<exr::meta::MetaData>::collect_ordered_block_data<<exr::image::write::WriteImageWithOptions<exr::image::Layer<exr::image::SpecificChannels<image::codecs::openexr::write_buffer<&mut std::io::cursor::Cursor<alloc::vec::Vec<u8>>>::{closure#0}, (exr::meta::attribute::ChannelDescription, exr::meta::attribute::ChannelDescription, exr::meta::attribute::ChannelDescription)>>, fn(f64)>>::to_buffered<&mut &mut std::io::cursor::Cursor<alloc::vec::Vec<u8>>>::{closure#0}::{closure#0}>::{closure#0}>::{closure#0}>>
Unexecuted instantiation: <exr::block::writer::OnProgressChunkWriter<exr::block::writer::ChunkWriter<&mut &mut std::io::cursor::Cursor<alloc::vec::Vec<u8>>>, fn(f64)> as exr::block::writer::ChunksWriter>::compress_all_blocks_parallel::<core::iter::adapters::map::Map<core::iter::adapters::flatten::FlatMap<core::iter::adapters::enumerate::Enumerate<core::slice::iter::Iter<exr::meta::header::Header>>, core::iter::adapters::map::Map<alloc::boxed::Box<dyn core::iter::traits::iterator::Iterator<Item = (usize, exr::meta::TileIndices)> + core::marker::Send>, exr::block::enumerate_ordered_header_block_indices::{closure#0}::{closure#0}>, exr::block::enumerate_ordered_header_block_indices::{closure#0}>, <exr::meta::MetaData>::collect_ordered_blocks<<exr::meta::MetaData>::collect_ordered_block_data<<exr::image::write::WriteImageWithOptions<exr::image::Layer<exr::image::SpecificChannels<image::codecs::openexr::write_buffer<&mut std::io::cursor::Cursor<alloc::vec::Vec<u8>>>::{closure#1}, (exr::meta::attribute::ChannelDescription, exr::meta::attribute::ChannelDescription, exr::meta::attribute::ChannelDescription, exr::meta::attribute::ChannelDescription)>>, fn(f64)>>::to_buffered<&mut &mut std::io::cursor::Cursor<alloc::vec::Vec<u8>>>::{closure#0}::{closure#0}>::{closure#0}>::{closure#0}>>
Unexecuted instantiation: <exr::block::writer::OnProgressChunkWriter<exr::block::writer::ChunkWriter<&mut std::io::cursor::Cursor<&mut alloc::vec::Vec<u8>>>, fn(f64)> as exr::block::writer::ChunksWriter>::compress_all_blocks_parallel::<core::iter::adapters::map::Map<core::iter::adapters::flatten::FlatMap<core::iter::adapters::enumerate::Enumerate<core::slice::iter::Iter<exr::meta::header::Header>>, core::iter::adapters::map::Map<alloc::boxed::Box<dyn core::iter::traits::iterator::Iterator<Item = (usize, exr::meta::TileIndices)> + core::marker::Send>, exr::block::enumerate_ordered_header_block_indices::{closure#0}::{closure#0}>, exr::block::enumerate_ordered_header_block_indices::{closure#0}>, <exr::meta::MetaData>::collect_ordered_blocks<<exr::meta::MetaData>::collect_ordered_block_data<<exr::image::write::WriteImageWithOptions<exr::image::Layer<exr::image::SpecificChannels<image::codecs::openexr::write_buffer<std::io::cursor::Cursor<&mut alloc::vec::Vec<u8>>>::{closure#0}, (exr::meta::attribute::ChannelDescription, exr::meta::attribute::ChannelDescription, exr::meta::attribute::ChannelDescription)>>, fn(f64)>>::to_buffered<&mut std::io::cursor::Cursor<&mut alloc::vec::Vec<u8>>>::{closure#0}::{closure#0}>::{closure#0}>::{closure#0}>>
<exr::block::writer::OnProgressChunkWriter<exr::block::writer::ChunkWriter<&mut std::io::cursor::Cursor<&mut alloc::vec::Vec<u8>>>, fn(f64)> as exr::block::writer::ChunksWriter>::compress_all_blocks_parallel::<core::iter::adapters::map::Map<core::iter::adapters::flatten::FlatMap<core::iter::adapters::enumerate::Enumerate<core::slice::iter::Iter<exr::meta::header::Header>>, core::iter::adapters::map::Map<alloc::boxed::Box<dyn core::iter::traits::iterator::Iterator<Item = (usize, exr::meta::TileIndices)> + core::marker::Send>, exr::block::enumerate_ordered_header_block_indices::{closure#0}::{closure#0}>, exr::block::enumerate_ordered_header_block_indices::{closure#0}>, <exr::meta::MetaData>::collect_ordered_blocks<<exr::meta::MetaData>::collect_ordered_block_data<<exr::image::write::WriteImageWithOptions<exr::image::Layer<exr::image::SpecificChannels<image::codecs::openexr::write_buffer<std::io::cursor::Cursor<&mut alloc::vec::Vec<u8>>>::{closure#1}, (exr::meta::attribute::ChannelDescription, exr::meta::attribute::ChannelDescription, exr::meta::attribute::ChannelDescription, exr::meta::attribute::ChannelDescription)>>, fn(f64)>>::to_buffered<&mut std::io::cursor::Cursor<&mut alloc::vec::Vec<u8>>>::{closure#0}::{closure#0}>::{closure#0}>::{closure#0}>>
Line
Count
Source
128
78
    fn compress_all_blocks_parallel(
129
78
        mut self,
130
78
        meta: &MetaData,
131
78
        blocks: impl Iterator<Item = (usize, UncompressedBlock)>,
132
78
    ) -> UnitResult {
133
78
        let mut parallel_writer = match self.parallel_blocks_compressor(meta) {
134
0
            None => return self.compress_all_blocks_sequential(meta, blocks),
135
78
            Some(writer) => writer,
136
        };
137
138
        // TODO check block order if line order is not unspecified!
139
722k
        for (index_in_header_increasing_y, block) in blocks {
140
722k
            parallel_writer.add_block_to_compression_queue(index_in_header_increasing_y, block)?;
141
        }
142
143
        // TODO debug_assert_eq!(self.is_complete());
144
78
        Ok(())
145
78
    }
146
}
147
148
impl<W> ChunksWriter for ChunkWriter<W>
149
where
150
    W: Write + Seek,
151
{
152
    /// The total number of chunks that the complete file will contain.
153
1.44M
    fn total_chunks_count(&self) -> usize {
154
1.44M
        self.chunk_count
155
1.44M
    }
Unexecuted instantiation: <exr::block::writer::ChunkWriter<_> as exr::block::writer::ChunksWriter>::total_chunks_count
Unexecuted instantiation: <exr::block::writer::ChunkWriter<&mut &mut std::io::cursor::Cursor<alloc::vec::Vec<u8>>> as exr::block::writer::ChunksWriter>::total_chunks_count
<exr::block::writer::ChunkWriter<&mut std::io::cursor::Cursor<&mut alloc::vec::Vec<u8>>> as exr::block::writer::ChunksWriter>::total_chunks_count
Line
Count
Source
153
1.44M
    fn total_chunks_count(&self) -> usize {
154
1.44M
        self.chunk_count
155
1.44M
    }
156
157
    /// Any more calls will result in an error and have no effect.
158
    /// If writing results in an error, the file and the writer
159
    /// may remain in an invalid state and should not be used further.
160
    /// Errors when the chunk at this index was already written.
161
722k
    fn write_chunk(&mut self, index_in_header_increasing_y: usize, chunk: Chunk) -> UnitResult {
162
722k
        let header_chunk_indices = &mut self.chunk_indices_increasing_y[chunk.layer_index];
163
164
722k
        if index_in_header_increasing_y >= header_chunk_indices.len() {
165
0
            return Err(Error::invalid(format!(
166
0
                "chunk index {} exceeds maximum {} for layer {}",
167
0
                index_in_header_increasing_y,
168
0
                header_chunk_indices.len().saturating_sub(1),
169
0
                chunk.layer_index
170
0
            )));
171
722k
        }
172
173
722k
        let chunk_index_slot = &mut header_chunk_indices[index_in_header_increasing_y];
174
722k
        if *chunk_index_slot != 0 {
175
0
            return Err(Error::invalid(format!(
176
0
                "chunk at index {} is already written",
177
0
                index_in_header_increasing_y
178
0
            )));
179
722k
        }
180
181
722k
        *chunk_index_slot = usize_to_u64(self.byte_writer.byte_position(), "seek position")?;
182
722k
        chunk.write(&mut self.byte_writer, self.header_count)?;
183
722k
        Ok(())
184
722k
    }
Unexecuted instantiation: <exr::block::writer::ChunkWriter<_> as exr::block::writer::ChunksWriter>::write_chunk
Unexecuted instantiation: <exr::block::writer::ChunkWriter<&mut &mut std::io::cursor::Cursor<alloc::vec::Vec<u8>>> as exr::block::writer::ChunksWriter>::write_chunk
<exr::block::writer::ChunkWriter<&mut std::io::cursor::Cursor<&mut alloc::vec::Vec<u8>>> as exr::block::writer::ChunksWriter>::write_chunk
Line
Count
Source
161
722k
    fn write_chunk(&mut self, index_in_header_increasing_y: usize, chunk: Chunk) -> UnitResult {
162
722k
        let header_chunk_indices = &mut self.chunk_indices_increasing_y[chunk.layer_index];
163
164
722k
        if index_in_header_increasing_y >= header_chunk_indices.len() {
165
0
            return Err(Error::invalid(format!(
166
0
                "chunk index {} exceeds maximum {} for layer {}",
167
0
                index_in_header_increasing_y,
168
0
                header_chunk_indices.len().saturating_sub(1),
169
0
                chunk.layer_index
170
0
            )));
171
722k
        }
172
173
722k
        let chunk_index_slot = &mut header_chunk_indices[index_in_header_increasing_y];
174
722k
        if *chunk_index_slot != 0 {
175
0
            return Err(Error::invalid(format!(
176
0
                "chunk at index {} is already written",
177
0
                index_in_header_increasing_y
178
0
            )));
179
722k
        }
180
181
722k
        *chunk_index_slot = usize_to_u64(self.byte_writer.byte_position(), "seek position")?;
182
722k
        chunk.write(&mut self.byte_writer, self.header_count)?;
183
722k
        Ok(())
184
722k
    }
185
}
186
187
impl<W> ChunkWriter<W>
188
where
189
    W: Write + Seek,
190
{
191
    // -- the following functions are private, because they must be called in a
192
    // strict order --
193
194
    /// Writes the meta data and zeroed offset tables as a placeholder.
195
78
    fn new_for_buffered(
196
78
        buffered_byte_writer: W,
197
78
        headers: Headers,
198
78
        pedantic: bool,
199
78
    ) -> Result<(MetaData, Self)> {
200
78
        let mut write = Tracking::new(buffered_byte_writer);
201
78
        let requirements =
202
78
            MetaData::write_validating_to_buffered(&mut write, headers.as_slice(), pedantic)?;
203
204
        // TODO: use increasing line order where possible, but this requires us to know
205
        // whether we want to be parallel right now
206
        // // if non-parallel compression, we always use increasing order anyways
207
        // if !parallel || !has_compression {
208
        // for header in &mut headers {
209
        // if header.line_order == LineOrder::Unspecified {
210
        // header.line_order = LineOrder::Increasing;
211
        // }
212
        // }
213
        // }
214
215
78
        let offset_table_size: usize = headers.iter().map(|header| header.chunk_count).sum();
216
217
78
        let offset_table_start_byte = write.byte_position();
218
78
        let offset_table_end_byte = write.byte_position() + offset_table_size * u64::BYTE_SIZE;
219
220
        // skip offset tables, filling with 0, will be updated after the last chunk has
221
        // been written
222
78
        write.seek_write_to(offset_table_end_byte)?;
223
224
78
        let header_count = headers.len();
225
78
        let chunk_indices_increasing_y =
226
78
            headers.iter().map(|header| vec![0_u64; header.chunk_count]).collect();
Unexecuted instantiation: <exr::block::writer::ChunkWriter<_>>::new_for_buffered::{closure#1}
Unexecuted instantiation: <exr::block::writer::ChunkWriter<&mut &mut std::io::cursor::Cursor<alloc::vec::Vec<u8>>>>::new_for_buffered::{closure#1}
<exr::block::writer::ChunkWriter<&mut std::io::cursor::Cursor<&mut alloc::vec::Vec<u8>>>>::new_for_buffered::{closure#1}
Line
Count
Source
226
78
            headers.iter().map(|header| vec![0_u64; header.chunk_count]).collect();
227
228
78
        let meta_data = MetaData {
229
78
            requirements,
230
78
            headers,
231
78
        };
232
233
78
        Ok((
234
78
            meta_data,
235
78
            ChunkWriter {
236
78
                header_count,
237
78
                byte_writer: write,
238
78
                chunk_count: offset_table_size,
239
78
                chunk_indices_byte_location: offset_table_start_byte..offset_table_end_byte,
240
78
                chunk_indices_increasing_y,
241
78
            },
242
78
        ))
243
78
    }
Unexecuted instantiation: <exr::block::writer::ChunkWriter<_>>::new_for_buffered
Unexecuted instantiation: <exr::block::writer::ChunkWriter<&mut &mut std::io::cursor::Cursor<alloc::vec::Vec<u8>>>>::new_for_buffered
<exr::block::writer::ChunkWriter<&mut std::io::cursor::Cursor<&mut alloc::vec::Vec<u8>>>>::new_for_buffered
Line
Count
Source
195
78
    fn new_for_buffered(
196
78
        buffered_byte_writer: W,
197
78
        headers: Headers,
198
78
        pedantic: bool,
199
78
    ) -> Result<(MetaData, Self)> {
200
78
        let mut write = Tracking::new(buffered_byte_writer);
201
78
        let requirements =
202
78
            MetaData::write_validating_to_buffered(&mut write, headers.as_slice(), pedantic)?;
203
204
        // TODO: use increasing line order where possible, but this requires us to know
205
        // whether we want to be parallel right now
206
        // // if non-parallel compression, we always use increasing order anyways
207
        // if !parallel || !has_compression {
208
        // for header in &mut headers {
209
        // if header.line_order == LineOrder::Unspecified {
210
        // header.line_order = LineOrder::Increasing;
211
        // }
212
        // }
213
        // }
214
215
78
        let offset_table_size: usize = headers.iter().map(|header| header.chunk_count).sum();
216
217
78
        let offset_table_start_byte = write.byte_position();
218
78
        let offset_table_end_byte = write.byte_position() + offset_table_size * u64::BYTE_SIZE;
219
220
        // skip offset tables, filling with 0, will be updated after the last chunk has
221
        // been written
222
78
        write.seek_write_to(offset_table_end_byte)?;
223
224
78
        let header_count = headers.len();
225
78
        let chunk_indices_increasing_y =
226
78
            headers.iter().map(|header| vec![0_u64; header.chunk_count]).collect();
227
228
78
        let meta_data = MetaData {
229
78
            requirements,
230
78
            headers,
231
78
        };
232
233
78
        Ok((
234
78
            meta_data,
235
78
            ChunkWriter {
236
78
                header_count,
237
78
                byte_writer: write,
238
78
                chunk_count: offset_table_size,
239
78
                chunk_indices_byte_location: offset_table_start_byte..offset_table_end_byte,
240
78
                chunk_indices_increasing_y,
241
78
            },
242
78
        ))
243
78
    }
244
245
    /// Seek back to the meta data, write offset tables, and flush the byte
246
    /// writer. Leaves the writer seeked to the middle of the file,
247
    /// therefore we drop it.
248
78
    fn complete_meta_data(mut self) -> UnitResult {
249
722k
        if self.chunk_indices_increasing_y.iter().flatten().any(|&index| index == 0) {
Unexecuted instantiation: <exr::block::writer::ChunkWriter<_>>::complete_meta_data::{closure#0}
Unexecuted instantiation: <exr::block::writer::ChunkWriter<&mut &mut std::io::cursor::Cursor<alloc::vec::Vec<u8>>>>::complete_meta_data::{closure#0}
<exr::block::writer::ChunkWriter<&mut std::io::cursor::Cursor<&mut alloc::vec::Vec<u8>>>>::complete_meta_data::{closure#0}
Line
Count
Source
249
722k
        if self.chunk_indices_increasing_y.iter().flatten().any(|&index| index == 0) {
250
0
            return Err(Error::invalid("some chunks are not written yet"));
251
78
        }
252
253
        // write all offset tables
254
78
        debug_assert_ne!(
255
0
            self.byte_writer.byte_position(),
256
            self.chunk_indices_byte_location.end,
257
0
            "offset table has already been updated"
258
        );
259
78
        self.byte_writer.seek_write_to(self.chunk_indices_byte_location.start)?;
260
261
156
        for table in self.chunk_indices_increasing_y {
262
78
            u64::write_slice_le(&mut self.byte_writer, table.as_slice())?;
263
        }
264
265
78
        self.byte_writer.flush()?; // make sure we catch all (possibly delayed) io errors before returning
266
78
        Ok(())
267
78
    }
Unexecuted instantiation: <exr::block::writer::ChunkWriter<_>>::complete_meta_data
Unexecuted instantiation: <exr::block::writer::ChunkWriter<&mut &mut std::io::cursor::Cursor<alloc::vec::Vec<u8>>>>::complete_meta_data
<exr::block::writer::ChunkWriter<&mut std::io::cursor::Cursor<&mut alloc::vec::Vec<u8>>>>::complete_meta_data
Line
Count
Source
248
78
    fn complete_meta_data(mut self) -> UnitResult {
249
78
        if self.chunk_indices_increasing_y.iter().flatten().any(|&index| index == 0) {
250
0
            return Err(Error::invalid("some chunks are not written yet"));
251
78
        }
252
253
        // write all offset tables
254
78
        debug_assert_ne!(
255
0
            self.byte_writer.byte_position(),
256
            self.chunk_indices_byte_location.end,
257
0
            "offset table has already been updated"
258
        );
259
78
        self.byte_writer.seek_write_to(self.chunk_indices_byte_location.start)?;
260
261
156
        for table in self.chunk_indices_increasing_y {
262
78
            u64::write_slice_le(&mut self.byte_writer, table.as_slice())?;
263
        }
264
265
78
        self.byte_writer.flush()?; // make sure we catch all (possibly delayed) io errors before returning
266
78
        Ok(())
267
78
    }
268
}
269
270
impl<'w, W, F> ChunksWriter for OnProgressChunkWriter<'w, W, F>
271
where
272
    W: 'w + ChunksWriter,
273
    F: FnMut(f64),
274
{
275
1.44M
    fn total_chunks_count(&self) -> usize {
276
1.44M
        self.chunk_writer.total_chunks_count()
277
1.44M
    }
Unexecuted instantiation: <exr::block::writer::OnProgressChunkWriter<_, _> as exr::block::writer::ChunksWriter>::total_chunks_count
Unexecuted instantiation: <exr::block::writer::OnProgressChunkWriter<exr::block::writer::ChunkWriter<&mut &mut std::io::cursor::Cursor<alloc::vec::Vec<u8>>>, fn(f64)> as exr::block::writer::ChunksWriter>::total_chunks_count
<exr::block::writer::OnProgressChunkWriter<exr::block::writer::ChunkWriter<&mut std::io::cursor::Cursor<&mut alloc::vec::Vec<u8>>>, fn(f64)> as exr::block::writer::ChunksWriter>::total_chunks_count
Line
Count
Source
275
1.44M
    fn total_chunks_count(&self) -> usize {
276
1.44M
        self.chunk_writer.total_chunks_count()
277
1.44M
    }
278
279
722k
    fn write_chunk(&mut self, index_in_header_increasing_y: usize, chunk: Chunk) -> UnitResult {
280
722k
        let total_chunks = self.total_chunks_count();
281
722k
        let on_progress = &mut self.on_progress;
282
283
        // guarantee on_progress being called with 0 once
284
722k
        if self.written_chunks == 0 {
285
78
            on_progress(0.0);
286
722k
        }
287
288
722k
        self.chunk_writer.write_chunk(index_in_header_increasing_y, chunk)?;
289
290
722k
        self.written_chunks += 1;
291
292
722k
        on_progress({
293
            // guarantee finishing with progress 1.0 for last block at least once, float
294
            // division might slightly differ from 1.0
295
722k
            if self.written_chunks == total_chunks {
296
78
                1.0
297
            } else {
298
722k
                self.written_chunks as f64 / total_chunks as f64
299
            }
300
        });
301
302
722k
        Ok(())
303
722k
    }
Unexecuted instantiation: <exr::block::writer::OnProgressChunkWriter<_, _> as exr::block::writer::ChunksWriter>::write_chunk
Unexecuted instantiation: <exr::block::writer::OnProgressChunkWriter<exr::block::writer::ChunkWriter<&mut &mut std::io::cursor::Cursor<alloc::vec::Vec<u8>>>, fn(f64)> as exr::block::writer::ChunksWriter>::write_chunk
<exr::block::writer::OnProgressChunkWriter<exr::block::writer::ChunkWriter<&mut std::io::cursor::Cursor<&mut alloc::vec::Vec<u8>>>, fn(f64)> as exr::block::writer::ChunksWriter>::write_chunk
Line
Count
Source
279
722k
    fn write_chunk(&mut self, index_in_header_increasing_y: usize, chunk: Chunk) -> UnitResult {
280
722k
        let total_chunks = self.total_chunks_count();
281
722k
        let on_progress = &mut self.on_progress;
282
283
        // guarantee on_progress being called with 0 once
284
722k
        if self.written_chunks == 0 {
285
78
            on_progress(0.0);
286
722k
        }
287
288
722k
        self.chunk_writer.write_chunk(index_in_header_increasing_y, chunk)?;
289
290
722k
        self.written_chunks += 1;
291
292
722k
        on_progress({
293
            // guarantee finishing with progress 1.0 for last block at least once, float
294
            // division might slightly differ from 1.0
295
722k
            if self.written_chunks == total_chunks {
296
78
                1.0
297
            } else {
298
722k
                self.written_chunks as f64 / total_chunks as f64
299
            }
300
        });
301
302
722k
        Ok(())
303
722k
    }
304
}
305
306
/// Write blocks that appear in any order and reorder them before writing.
307
#[derive(Debug)]
308
#[must_use]
309
pub struct SortedBlocksWriter<'w, W> {
310
    chunk_writer: &'w mut W,
311
    pending_chunks: BTreeMap<usize, (usize, Chunk)>,
312
    unwritten_chunk_indices: Peekable<std::ops::Range<usize>>,
313
    requires_sorting: bool, // using this instead of Option, because of borrowing
314
}
315
316
impl<'w, W> SortedBlocksWriter<'w, W>
317
where
318
    W: ChunksWriter,
319
{
320
    /// New sorting writer. Returns `None` if sorting is not required.
321
78
    pub fn new(meta_data: &MetaData, chunk_writer: &'w mut W) -> SortedBlocksWriter<'w, W> {
322
78
        let requires_sorting =
323
78
            meta_data.headers.iter().any(|header| header.line_order != LineOrder::Unspecified);
Unexecuted instantiation: <exr::block::writer::SortedBlocksWriter<_>>::new::{closure#0}
Unexecuted instantiation: <exr::block::writer::SortedBlocksWriter<exr::block::writer::OnProgressChunkWriter<exr::block::writer::ChunkWriter<&mut &mut std::io::cursor::Cursor<alloc::vec::Vec<u8>>>, fn(f64)>>>::new::{closure#0}
<exr::block::writer::SortedBlocksWriter<exr::block::writer::OnProgressChunkWriter<exr::block::writer::ChunkWriter<&mut std::io::cursor::Cursor<&mut alloc::vec::Vec<u8>>>, fn(f64)>>>::new::{closure#0}
Line
Count
Source
323
78
            meta_data.headers.iter().any(|header| header.line_order != LineOrder::Unspecified);
324
325
78
        let total_chunk_count = chunk_writer.total_chunks_count();
326
327
78
        SortedBlocksWriter {
328
78
            pending_chunks: BTreeMap::new(),
329
78
            unwritten_chunk_indices: (0..total_chunk_count).peekable(),
330
78
            requires_sorting,
331
78
            chunk_writer,
332
78
        }
333
78
    }
Unexecuted instantiation: <exr::block::writer::SortedBlocksWriter<_>>::new
Unexecuted instantiation: <exr::block::writer::SortedBlocksWriter<exr::block::writer::OnProgressChunkWriter<exr::block::writer::ChunkWriter<&mut &mut std::io::cursor::Cursor<alloc::vec::Vec<u8>>>, fn(f64)>>>::new
<exr::block::writer::SortedBlocksWriter<exr::block::writer::OnProgressChunkWriter<exr::block::writer::ChunkWriter<&mut std::io::cursor::Cursor<&mut alloc::vec::Vec<u8>>>, fn(f64)>>>::new
Line
Count
Source
321
78
    pub fn new(meta_data: &MetaData, chunk_writer: &'w mut W) -> SortedBlocksWriter<'w, W> {
322
78
        let requires_sorting =
323
78
            meta_data.headers.iter().any(|header| header.line_order != LineOrder::Unspecified);
324
325
78
        let total_chunk_count = chunk_writer.total_chunks_count();
326
327
78
        SortedBlocksWriter {
328
78
            pending_chunks: BTreeMap::new(),
329
78
            unwritten_chunk_indices: (0..total_chunk_count).peekable(),
330
78
            requires_sorting,
331
78
            chunk_writer,
332
78
        }
333
78
    }
334
335
    /// Write the chunk or stash it. In the closure, write all chunks that can
336
    /// be written now.
337
722k
    pub fn write_or_stash_chunk(
338
722k
        &mut self,
339
722k
        chunk_index_in_file: usize,
340
722k
        chunk_y_index: usize,
341
722k
        chunk: Chunk,
342
722k
    ) -> UnitResult {
343
722k
        if self.requires_sorting.not() {
344
722k
            return self.chunk_writer.write_chunk(chunk_y_index, chunk);
345
0
        }
346
347
        // write this chunk now if possible
348
0
        if self.unwritten_chunk_indices.peek() == Some(&chunk_index_in_file) {
349
0
            self.chunk_writer.write_chunk(chunk_y_index, chunk)?;
350
0
            self.unwritten_chunk_indices.next().expect("peeked chunk index is missing");
351
352
            // write all pending blocks that are immediate successors of this block
353
0
            while let Some((next_chunk_y_index, next_chunk)) = self
354
0
                .unwritten_chunk_indices
355
0
                .peek()
356
0
                .cloned()
357
0
                .and_then(|id| self.pending_chunks.remove(&id))
Unexecuted instantiation: <exr::block::writer::SortedBlocksWriter<_>>::write_or_stash_chunk::{closure#0}
Unexecuted instantiation: <exr::block::writer::SortedBlocksWriter<exr::block::writer::OnProgressChunkWriter<exr::block::writer::ChunkWriter<&mut &mut std::io::cursor::Cursor<alloc::vec::Vec<u8>>>, fn(f64)>>>::write_or_stash_chunk::{closure#0}
Unexecuted instantiation: <exr::block::writer::SortedBlocksWriter<exr::block::writer::OnProgressChunkWriter<exr::block::writer::ChunkWriter<&mut std::io::cursor::Cursor<&mut alloc::vec::Vec<u8>>>, fn(f64)>>>::write_or_stash_chunk::{closure#0}
358
            {
359
0
                self.chunk_writer.write_chunk(next_chunk_y_index, next_chunk)?;
360
0
                self.unwritten_chunk_indices.next().expect("peeked chunk index is missing");
361
            }
362
0
        } else {
363
0
            // the argument block is not to be written now,
364
0
            // and all the pending blocks are not next up either,
365
0
            // so just stash this block
366
0
            self.pending_chunks.insert(chunk_index_in_file, (chunk_y_index, chunk));
367
0
        }
368
369
0
        Ok(())
370
722k
    }
Unexecuted instantiation: <exr::block::writer::SortedBlocksWriter<_>>::write_or_stash_chunk
Unexecuted instantiation: <exr::block::writer::SortedBlocksWriter<exr::block::writer::OnProgressChunkWriter<exr::block::writer::ChunkWriter<&mut &mut std::io::cursor::Cursor<alloc::vec::Vec<u8>>>, fn(f64)>>>::write_or_stash_chunk
<exr::block::writer::SortedBlocksWriter<exr::block::writer::OnProgressChunkWriter<exr::block::writer::ChunkWriter<&mut std::io::cursor::Cursor<&mut alloc::vec::Vec<u8>>>, fn(f64)>>>::write_or_stash_chunk
Line
Count
Source
337
722k
    pub fn write_or_stash_chunk(
338
722k
        &mut self,
339
722k
        chunk_index_in_file: usize,
340
722k
        chunk_y_index: usize,
341
722k
        chunk: Chunk,
342
722k
    ) -> UnitResult {
343
722k
        if self.requires_sorting.not() {
344
722k
            return self.chunk_writer.write_chunk(chunk_y_index, chunk);
345
0
        }
346
347
        // write this chunk now if possible
348
0
        if self.unwritten_chunk_indices.peek() == Some(&chunk_index_in_file) {
349
0
            self.chunk_writer.write_chunk(chunk_y_index, chunk)?;
350
0
            self.unwritten_chunk_indices.next().expect("peeked chunk index is missing");
351
352
            // write all pending blocks that are immediate successors of this block
353
0
            while let Some((next_chunk_y_index, next_chunk)) = self
354
0
                .unwritten_chunk_indices
355
0
                .peek()
356
0
                .cloned()
357
0
                .and_then(|id| self.pending_chunks.remove(&id))
358
            {
359
0
                self.chunk_writer.write_chunk(next_chunk_y_index, next_chunk)?;
360
0
                self.unwritten_chunk_indices.next().expect("peeked chunk index is missing");
361
            }
362
0
        } else {
363
0
            // the argument block is not to be written now,
364
0
            // and all the pending blocks are not next up either,
365
0
            // so just stash this block
366
0
            self.pending_chunks.insert(chunk_index_in_file, (chunk_y_index, chunk));
367
0
        }
368
369
0
        Ok(())
370
722k
    }
371
372
    /// Where the chunks will be written to.
373
722k
    pub fn inner_chunks_writer(&self) -> &W {
374
722k
        &self.chunk_writer
375
722k
    }
Unexecuted instantiation: <exr::block::writer::SortedBlocksWriter<_>>::inner_chunks_writer
Unexecuted instantiation: <exr::block::writer::SortedBlocksWriter<exr::block::writer::OnProgressChunkWriter<exr::block::writer::ChunkWriter<&mut &mut std::io::cursor::Cursor<alloc::vec::Vec<u8>>>, fn(f64)>>>::inner_chunks_writer
<exr::block::writer::SortedBlocksWriter<exr::block::writer::OnProgressChunkWriter<exr::block::writer::ChunkWriter<&mut std::io::cursor::Cursor<&mut alloc::vec::Vec<u8>>>, fn(f64)>>>::inner_chunks_writer
Line
Count
Source
373
722k
    pub fn inner_chunks_writer(&self) -> &W {
374
722k
        &self.chunk_writer
375
722k
    }
376
}
377
378
/// Compress blocks to a chunk writer in this thread.
379
#[derive(Debug)]
380
#[must_use]
381
pub struct SequentialBlocksCompressor<'w, W> {
382
    meta: &'w MetaData,
383
    chunks_writer: &'w mut W,
384
}
385
386
impl<'w, W> SequentialBlocksCompressor<'w, W>
387
where
388
    W: 'w + ChunksWriter,
389
{
390
    /// New blocks writer.
391
0
    pub fn new(meta: &'w MetaData, chunks_writer: &'w mut W) -> Self {
392
0
        Self {
393
0
            meta,
394
0
            chunks_writer,
395
0
        }
396
0
    }
Unexecuted instantiation: <exr::block::writer::SequentialBlocksCompressor<_>>::new
Unexecuted instantiation: <exr::block::writer::SequentialBlocksCompressor<exr::block::writer::OnProgressChunkWriter<exr::block::writer::ChunkWriter<&mut &mut std::io::cursor::Cursor<alloc::vec::Vec<u8>>>, fn(f64)>>>::new
Unexecuted instantiation: <exr::block::writer::SequentialBlocksCompressor<exr::block::writer::OnProgressChunkWriter<exr::block::writer::ChunkWriter<&mut std::io::cursor::Cursor<&mut alloc::vec::Vec<u8>>>, fn(f64)>>>::new
397
398
    /// This is where the compressed blocks are written to.
399
0
    pub fn inner_chunks_writer(&'w self) -> &'w W {
400
0
        self.chunks_writer
401
0
    }
402
403
    /// Compress a single block immediately. The index of the block must be in
404
    /// increasing line order.
405
0
    pub fn compress_block(
406
0
        &mut self,
407
0
        index_in_header_increasing_y: usize,
408
0
        block: UncompressedBlock,
409
0
    ) -> UnitResult {
410
0
        self.chunks_writer
411
0
            .write_chunk(index_in_header_increasing_y, block.compress_to_chunk(&self.meta.headers)?)
412
0
    }
Unexecuted instantiation: <exr::block::writer::SequentialBlocksCompressor<_>>::compress_block
Unexecuted instantiation: <exr::block::writer::SequentialBlocksCompressor<exr::block::writer::OnProgressChunkWriter<exr::block::writer::ChunkWriter<&mut &mut std::io::cursor::Cursor<alloc::vec::Vec<u8>>>, fn(f64)>>>::compress_block
Unexecuted instantiation: <exr::block::writer::SequentialBlocksCompressor<exr::block::writer::OnProgressChunkWriter<exr::block::writer::ChunkWriter<&mut std::io::cursor::Cursor<&mut alloc::vec::Vec<u8>>>, fn(f64)>>>::compress_block
413
}
414
415
/// Compress blocks to a chunk writer with multiple threads.
416
#[cfg(feature = "rayon")]
417
#[derive(Debug)]
418
#[must_use]
419
pub struct ParallelBlocksCompressor<'w, W> {
420
    meta: &'w MetaData,
421
    sorted_writer: SortedBlocksWriter<'w, W>,
422
423
    sender: std::sync::mpsc::Sender<Result<(usize, usize, Chunk)>>,
424
    receiver: std::sync::mpsc::Receiver<Result<(usize, usize, Chunk)>>,
425
    pool: rayon_core::ThreadPool,
426
427
    currently_compressing_count: usize,
428
    written_chunk_count: usize, // used to check for last chunk
429
    max_threads: usize,
430
    next_incoming_chunk_index: usize, // used to remember original chunk order
431
}
432
433
#[cfg(feature = "rayon")]
434
impl<'w, W> ParallelBlocksCompressor<'w, W>
435
where
436
    W: 'w + ChunksWriter,
437
{
438
    /// New blocks writer. Returns none if sequential compression should be
439
    /// used. Use `new_with_thread_pool` to customize the threadpool.
440
78
    pub fn new(meta: &'w MetaData, chunks_writer: &'w mut W) -> Option<Self> {
441
78
        Self::new_with_thread_pool(meta, chunks_writer, || {
442
78
            rayon_core::ThreadPoolBuilder::new()
443
2.49k
                .thread_name(|index| format!("OpenEXR Block Compressor Thread #{}", index))
Unexecuted instantiation: <exr::block::writer::ParallelBlocksCompressor<_>>::new::{closure#0}::{closure#0}
Unexecuted instantiation: <exr::block::writer::ParallelBlocksCompressor<exr::block::writer::OnProgressChunkWriter<exr::block::writer::ChunkWriter<&mut &mut std::io::cursor::Cursor<alloc::vec::Vec<u8>>>, fn(f64)>>>::new::{closure#0}::{closure#0}
<exr::block::writer::ParallelBlocksCompressor<exr::block::writer::OnProgressChunkWriter<exr::block::writer::ChunkWriter<&mut std::io::cursor::Cursor<&mut alloc::vec::Vec<u8>>>, fn(f64)>>>::new::{closure#0}::{closure#0}
Line
Count
Source
443
2.49k
                .thread_name(|index| format!("OpenEXR Block Compressor Thread #{}", index))
444
78
                .build()
445
78
        })
Unexecuted instantiation: <exr::block::writer::ParallelBlocksCompressor<_>>::new::{closure#0}
Unexecuted instantiation: <exr::block::writer::ParallelBlocksCompressor<exr::block::writer::OnProgressChunkWriter<exr::block::writer::ChunkWriter<&mut &mut std::io::cursor::Cursor<alloc::vec::Vec<u8>>>, fn(f64)>>>::new::{closure#0}
<exr::block::writer::ParallelBlocksCompressor<exr::block::writer::OnProgressChunkWriter<exr::block::writer::ChunkWriter<&mut std::io::cursor::Cursor<&mut alloc::vec::Vec<u8>>>, fn(f64)>>>::new::{closure#0}
Line
Count
Source
441
78
        Self::new_with_thread_pool(meta, chunks_writer, || {
442
78
            rayon_core::ThreadPoolBuilder::new()
443
78
                .thread_name(|index| format!("OpenEXR Block Compressor Thread #{}", index))
444
78
                .build()
445
78
        })
446
78
    }
Unexecuted instantiation: <exr::block::writer::ParallelBlocksCompressor<_>>::new
Unexecuted instantiation: <exr::block::writer::ParallelBlocksCompressor<exr::block::writer::OnProgressChunkWriter<exr::block::writer::ChunkWriter<&mut &mut std::io::cursor::Cursor<alloc::vec::Vec<u8>>>, fn(f64)>>>::new
<exr::block::writer::ParallelBlocksCompressor<exr::block::writer::OnProgressChunkWriter<exr::block::writer::ChunkWriter<&mut std::io::cursor::Cursor<&mut alloc::vec::Vec<u8>>>, fn(f64)>>>::new
Line
Count
Source
440
78
    pub fn new(meta: &'w MetaData, chunks_writer: &'w mut W) -> Option<Self> {
441
78
        Self::new_with_thread_pool(meta, chunks_writer, || {
442
            rayon_core::ThreadPoolBuilder::new()
443
                .thread_name(|index| format!("OpenEXR Block Compressor Thread #{}", index))
444
                .build()
445
        })
446
78
    }
447
448
    /// New blocks writer. Returns none if sequential compression should be
449
    /// used.
450
78
    pub fn new_with_thread_pool<CreatePool>(
451
78
        meta: &'w MetaData,
452
78
        chunks_writer: &'w mut W,
453
78
        try_create_thread_pool: CreatePool,
454
78
    ) -> Option<Self>
455
78
    where
456
78
        CreatePool:
457
78
            FnOnce()
458
78
                -> std::result::Result<rayon_core::ThreadPool, rayon_core::ThreadPoolBuildError>,
459
    {
460
        use crate::compression::Compression;
461
462
78
        let is_entirely_uncompressed =
463
78
            meta.headers.iter().all(|head| head.compression == Compression::Uncompressed);
Unexecuted instantiation: <exr::block::writer::ParallelBlocksCompressor<_>>::new_with_thread_pool::<_>::{closure#0}
Unexecuted instantiation: <exr::block::writer::ParallelBlocksCompressor<exr::block::writer::OnProgressChunkWriter<exr::block::writer::ChunkWriter<&mut &mut std::io::cursor::Cursor<alloc::vec::Vec<u8>>>, fn(f64)>>>::new_with_thread_pool::<<exr::block::writer::ParallelBlocksCompressor<exr::block::writer::OnProgressChunkWriter<exr::block::writer::ChunkWriter<&mut &mut std::io::cursor::Cursor<alloc::vec::Vec<u8>>>, fn(f64)>>>::new::{closure#0}>::{closure#0}
<exr::block::writer::ParallelBlocksCompressor<exr::block::writer::OnProgressChunkWriter<exr::block::writer::ChunkWriter<&mut std::io::cursor::Cursor<&mut alloc::vec::Vec<u8>>>, fn(f64)>>>::new_with_thread_pool::<<exr::block::writer::ParallelBlocksCompressor<exr::block::writer::OnProgressChunkWriter<exr::block::writer::ChunkWriter<&mut std::io::cursor::Cursor<&mut alloc::vec::Vec<u8>>>, fn(f64)>>>::new::{closure#0}>::{closure#0}
Line
Count
Source
463
78
            meta.headers.iter().all(|head| head.compression == Compression::Uncompressed);
464
465
78
        if is_entirely_uncompressed {
466
0
            return None;
467
78
        }
468
469
        // in case thread pool creation fails (for example on WASM currently),
470
        // we revert to sequential compression
471
78
        let pool = match try_create_thread_pool() {
472
78
            Ok(pool) => pool,
473
474
            // TODO print warning?
475
0
            Err(_) => return None,
476
        };
477
478
78
        let max_threads =
479
78
            pool.current_num_threads().max(1).min(chunks_writer.total_chunks_count()) + 2; // ca one block for each thread at all times
480
78
        let (send, recv) = std::sync::mpsc::channel(); // TODO bounded channel simplifies logic?
481
482
78
        Some(Self {
483
78
            sorted_writer: SortedBlocksWriter::new(meta, chunks_writer),
484
78
            next_incoming_chunk_index: 0,
485
78
            currently_compressing_count: 0,
486
78
            written_chunk_count: 0,
487
78
            sender: send,
488
78
            receiver: recv,
489
78
            max_threads,
490
78
            pool,
491
78
            meta,
492
78
        })
493
78
    }
Unexecuted instantiation: <exr::block::writer::ParallelBlocksCompressor<_>>::new_with_thread_pool::<_>
Unexecuted instantiation: <exr::block::writer::ParallelBlocksCompressor<exr::block::writer::OnProgressChunkWriter<exr::block::writer::ChunkWriter<&mut &mut std::io::cursor::Cursor<alloc::vec::Vec<u8>>>, fn(f64)>>>::new_with_thread_pool::<<exr::block::writer::ParallelBlocksCompressor<exr::block::writer::OnProgressChunkWriter<exr::block::writer::ChunkWriter<&mut &mut std::io::cursor::Cursor<alloc::vec::Vec<u8>>>, fn(f64)>>>::new::{closure#0}>
<exr::block::writer::ParallelBlocksCompressor<exr::block::writer::OnProgressChunkWriter<exr::block::writer::ChunkWriter<&mut std::io::cursor::Cursor<&mut alloc::vec::Vec<u8>>>, fn(f64)>>>::new_with_thread_pool::<<exr::block::writer::ParallelBlocksCompressor<exr::block::writer::OnProgressChunkWriter<exr::block::writer::ChunkWriter<&mut std::io::cursor::Cursor<&mut alloc::vec::Vec<u8>>>, fn(f64)>>>::new::{closure#0}>
Line
Count
Source
450
78
    pub fn new_with_thread_pool<CreatePool>(
451
78
        meta: &'w MetaData,
452
78
        chunks_writer: &'w mut W,
453
78
        try_create_thread_pool: CreatePool,
454
78
    ) -> Option<Self>
455
78
    where
456
78
        CreatePool:
457
78
            FnOnce()
458
78
                -> std::result::Result<rayon_core::ThreadPool, rayon_core::ThreadPoolBuildError>,
459
    {
460
        use crate::compression::Compression;
461
462
78
        let is_entirely_uncompressed =
463
78
            meta.headers.iter().all(|head| head.compression == Compression::Uncompressed);
464
465
78
        if is_entirely_uncompressed {
466
0
            return None;
467
78
        }
468
469
        // in case thread pool creation fails (for example on WASM currently),
470
        // we revert to sequential compression
471
78
        let pool = match try_create_thread_pool() {
472
78
            Ok(pool) => pool,
473
474
            // TODO print warning?
475
0
            Err(_) => return None,
476
        };
477
478
78
        let max_threads =
479
78
            pool.current_num_threads().max(1).min(chunks_writer.total_chunks_count()) + 2; // ca one block for each thread at all times
480
78
        let (send, recv) = std::sync::mpsc::channel(); // TODO bounded channel simplifies logic?
481
482
78
        Some(Self {
483
78
            sorted_writer: SortedBlocksWriter::new(meta, chunks_writer),
484
78
            next_incoming_chunk_index: 0,
485
78
            currently_compressing_count: 0,
486
78
            written_chunk_count: 0,
487
78
            sender: send,
488
78
            receiver: recv,
489
78
            max_threads,
490
78
            pool,
491
78
            meta,
492
78
        })
493
78
    }
494
495
    /// This is where the compressed blocks are written to.
496
722k
    pub fn inner_chunks_writer(&'w self) -> &'w W {
497
722k
        self.sorted_writer.inner_chunks_writer()
498
722k
    }
Unexecuted instantiation: <exr::block::writer::ParallelBlocksCompressor<_>>::inner_chunks_writer
Unexecuted instantiation: <exr::block::writer::ParallelBlocksCompressor<exr::block::writer::OnProgressChunkWriter<exr::block::writer::ChunkWriter<&mut &mut std::io::cursor::Cursor<alloc::vec::Vec<u8>>>, fn(f64)>>>::inner_chunks_writer
<exr::block::writer::ParallelBlocksCompressor<exr::block::writer::OnProgressChunkWriter<exr::block::writer::ChunkWriter<&mut std::io::cursor::Cursor<&mut alloc::vec::Vec<u8>>>, fn(f64)>>>::inner_chunks_writer
Line
Count
Source
496
722k
    pub fn inner_chunks_writer(&'w self) -> &'w W {
497
722k
        self.sorted_writer.inner_chunks_writer()
498
722k
    }
499
500
    // private, as may underflow counter in release mode
501
722k
    fn write_next_queued_chunk(&mut self) -> UnitResult {
502
722k
        debug_assert!(
503
0
            self.currently_compressing_count > 0,
504
            "cannot wait for chunks as there are none left"
505
        );
506
507
722k
        let some_compressed_chunk = self.receiver.recv().expect("cannot receive compressed block");
508
509
722k
        self.currently_compressing_count -= 1;
510
722k
        let (chunk_file_index, chunk_y_index, chunk) = some_compressed_chunk?;
511
722k
        self.sorted_writer.write_or_stash_chunk(chunk_file_index, chunk_y_index, chunk)?;
512
513
722k
        self.written_chunk_count += 1;
514
722k
        Ok(())
515
722k
    }
Unexecuted instantiation: <exr::block::writer::ParallelBlocksCompressor<_>>::write_next_queued_chunk
Unexecuted instantiation: <exr::block::writer::ParallelBlocksCompressor<exr::block::writer::OnProgressChunkWriter<exr::block::writer::ChunkWriter<&mut &mut std::io::cursor::Cursor<alloc::vec::Vec<u8>>>, fn(f64)>>>::write_next_queued_chunk
<exr::block::writer::ParallelBlocksCompressor<exr::block::writer::OnProgressChunkWriter<exr::block::writer::ChunkWriter<&mut std::io::cursor::Cursor<&mut alloc::vec::Vec<u8>>>, fn(f64)>>>::write_next_queued_chunk
Line
Count
Source
501
722k
    fn write_next_queued_chunk(&mut self) -> UnitResult {
502
722k
        debug_assert!(
503
0
            self.currently_compressing_count > 0,
504
            "cannot wait for chunks as there are none left"
505
        );
506
507
722k
        let some_compressed_chunk = self.receiver.recv().expect("cannot receive compressed block");
508
509
722k
        self.currently_compressing_count -= 1;
510
722k
        let (chunk_file_index, chunk_y_index, chunk) = some_compressed_chunk?;
511
722k
        self.sorted_writer.write_or_stash_chunk(chunk_file_index, chunk_y_index, chunk)?;
512
513
722k
        self.written_chunk_count += 1;
514
722k
        Ok(())
515
722k
    }
516
517
    /// Wait until all currently compressing chunks in the compressor have been
518
    /// written.
519
78
    pub fn write_all_queued_chunks(&mut self) -> UnitResult {
520
2.01k
        while self.currently_compressing_count > 0 {
521
1.93k
            self.write_next_queued_chunk()?;
522
        }
523
524
78
        debug_assert_eq!(self.currently_compressing_count, 0, "counter does not match block count");
525
78
        Ok(())
526
78
    }
Unexecuted instantiation: <exr::block::writer::ParallelBlocksCompressor<_>>::write_all_queued_chunks
Unexecuted instantiation: <exr::block::writer::ParallelBlocksCompressor<exr::block::writer::OnProgressChunkWriter<exr::block::writer::ChunkWriter<&mut &mut std::io::cursor::Cursor<alloc::vec::Vec<u8>>>, fn(f64)>>>::write_all_queued_chunks
<exr::block::writer::ParallelBlocksCompressor<exr::block::writer::OnProgressChunkWriter<exr::block::writer::ChunkWriter<&mut std::io::cursor::Cursor<&mut alloc::vec::Vec<u8>>>, fn(f64)>>>::write_all_queued_chunks
Line
Count
Source
519
78
    pub fn write_all_queued_chunks(&mut self) -> UnitResult {
520
2.01k
        while self.currently_compressing_count > 0 {
521
1.93k
            self.write_next_queued_chunk()?;
522
        }
523
524
78
        debug_assert_eq!(self.currently_compressing_count, 0, "counter does not match block count");
525
78
        Ok(())
526
78
    }
527
528
    /// Add a single block to the compressor queue. The index of the block must
529
    /// be in increasing line order. When calling this function for the last
530
    /// block, this method waits until all the blocks have been written.
531
    /// This only works when you write as many blocks as the image expects,
532
    /// otherwise you can use `wait_for_all_remaining_chunks`. Waits for a
533
    /// block from the queue to be written, if the queue already has enough
534
    /// items.
535
722k
    pub fn add_block_to_compression_queue(
536
722k
        &mut self,
537
722k
        index_in_header_increasing_y: usize,
538
722k
        block: UncompressedBlock,
539
722k
    ) -> UnitResult {
540
        // if pipe is full, block to wait for a slot to free up
541
722k
        if self.currently_compressing_count >= self.max_threads {
542
720k
            self.write_next_queued_chunk()?;
543
1.93k
        }
544
545
        // add the argument chunk to the compression queueue
546
722k
        let index_in_file = self.next_incoming_chunk_index;
547
722k
        let sender = self.sender.clone();
548
722k
        let meta = self.meta.clone();
549
550
722k
        self.pool.spawn(move || {
551
722k
            let compressed_or_err = block.compress_to_chunk(&meta.headers);
552
553
            // by now, decompressing could have failed in another thread.
554
            // the error is then already handled, so we simply
555
            // don't send the decompressed block and do nothing
556
            let _ =
557
722k
                sender.send(compressed_or_err.map(move |compressed| {
558
722k
                    (index_in_file, index_in_header_increasing_y, compressed)
559
722k
                }));
Unexecuted instantiation: <exr::block::writer::ParallelBlocksCompressor<_>>::add_block_to_compression_queue::{closure#0}::{closure#0}
Unexecuted instantiation: <exr::block::writer::ParallelBlocksCompressor<exr::block::writer::OnProgressChunkWriter<exr::block::writer::ChunkWriter<&mut &mut std::io::cursor::Cursor<alloc::vec::Vec<u8>>>, fn(f64)>>>::add_block_to_compression_queue::{closure#0}::{closure#0}
<exr::block::writer::ParallelBlocksCompressor<exr::block::writer::OnProgressChunkWriter<exr::block::writer::ChunkWriter<&mut std::io::cursor::Cursor<&mut alloc::vec::Vec<u8>>>, fn(f64)>>>::add_block_to_compression_queue::{closure#0}::{closure#0}
Line
Count
Source
557
722k
                sender.send(compressed_or_err.map(move |compressed| {
558
722k
                    (index_in_file, index_in_header_increasing_y, compressed)
559
722k
                }));
560
722k
        });
Unexecuted instantiation: <exr::block::writer::ParallelBlocksCompressor<_>>::add_block_to_compression_queue::{closure#0}
Unexecuted instantiation: <exr::block::writer::ParallelBlocksCompressor<exr::block::writer::OnProgressChunkWriter<exr::block::writer::ChunkWriter<&mut &mut std::io::cursor::Cursor<alloc::vec::Vec<u8>>>, fn(f64)>>>::add_block_to_compression_queue::{closure#0}
<exr::block::writer::ParallelBlocksCompressor<exr::block::writer::OnProgressChunkWriter<exr::block::writer::ChunkWriter<&mut std::io::cursor::Cursor<&mut alloc::vec::Vec<u8>>>, fn(f64)>>>::add_block_to_compression_queue::{closure#0}
Line
Count
Source
550
722k
        self.pool.spawn(move || {
551
722k
            let compressed_or_err = block.compress_to_chunk(&meta.headers);
552
553
            // by now, decompressing could have failed in another thread.
554
            // the error is then already handled, so we simply
555
            // don't send the decompressed block and do nothing
556
            let _ =
557
722k
                sender.send(compressed_or_err.map(move |compressed| {
558
                    (index_in_file, index_in_header_increasing_y, compressed)
559
                }));
560
722k
        });
561
562
722k
        self.currently_compressing_count += 1;
563
722k
        self.next_incoming_chunk_index += 1;
564
565
        // if this is the last chunk, wait for all chunks to complete before returning
566
722k
        if self.written_chunk_count + self.currently_compressing_count
567
722k
            == self.inner_chunks_writer().total_chunks_count()
568
        {
569
78
            self.write_all_queued_chunks()?;
570
78
            debug_assert_eq!(
571
                self.written_chunk_count,
572
0
                self.inner_chunks_writer().total_chunks_count(),
573
0
                "written chunk count mismatch"
574
            );
575
722k
        }
576
577
722k
        Ok(())
578
722k
    }
Unexecuted instantiation: <exr::block::writer::ParallelBlocksCompressor<_>>::add_block_to_compression_queue
Unexecuted instantiation: <exr::block::writer::ParallelBlocksCompressor<exr::block::writer::OnProgressChunkWriter<exr::block::writer::ChunkWriter<&mut &mut std::io::cursor::Cursor<alloc::vec::Vec<u8>>>, fn(f64)>>>::add_block_to_compression_queue
<exr::block::writer::ParallelBlocksCompressor<exr::block::writer::OnProgressChunkWriter<exr::block::writer::ChunkWriter<&mut std::io::cursor::Cursor<&mut alloc::vec::Vec<u8>>>, fn(f64)>>>::add_block_to_compression_queue
Line
Count
Source
535
722k
    pub fn add_block_to_compression_queue(
536
722k
        &mut self,
537
722k
        index_in_header_increasing_y: usize,
538
722k
        block: UncompressedBlock,
539
722k
    ) -> UnitResult {
540
        // if pipe is full, block to wait for a slot to free up
541
722k
        if self.currently_compressing_count >= self.max_threads {
542
720k
            self.write_next_queued_chunk()?;
543
1.93k
        }
544
545
        // add the argument chunk to the compression queueue
546
722k
        let index_in_file = self.next_incoming_chunk_index;
547
722k
        let sender = self.sender.clone();
548
722k
        let meta = self.meta.clone();
549
550
722k
        self.pool.spawn(move || {
551
            let compressed_or_err = block.compress_to_chunk(&meta.headers);
552
553
            // by now, decompressing could have failed in another thread.
554
            // the error is then already handled, so we simply
555
            // don't send the decompressed block and do nothing
556
            let _ =
557
                sender.send(compressed_or_err.map(move |compressed| {
558
                    (index_in_file, index_in_header_increasing_y, compressed)
559
                }));
560
        });
561
562
722k
        self.currently_compressing_count += 1;
563
722k
        self.next_incoming_chunk_index += 1;
564
565
        // if this is the last chunk, wait for all chunks to complete before returning
566
722k
        if self.written_chunk_count + self.currently_compressing_count
567
722k
            == self.inner_chunks_writer().total_chunks_count()
568
        {
569
78
            self.write_all_queued_chunks()?;
570
78
            debug_assert_eq!(
571
                self.written_chunk_count,
572
0
                self.inner_chunks_writer().total_chunks_count(),
573
0
                "written chunk count mismatch"
574
            );
575
722k
        }
576
577
722k
        Ok(())
578
722k
    }
579
}