zx0/compressor.rs
1use crate::{
2 MAX_OFFSET_ZX0,
3 MAX_OFFSET_ZX7
4};
5
6use crate::compress::{Block, compress};
7use crate::optimize::optimize;
8
9/// A struct containing a vector representing the compressed data, as well as metadata related to
10/// the compression operation.
11pub struct CompressionResult {
12 /// A vector containing the compressed output data.
13 pub output: Vec<u8>,
14
15 /// This value represents the minimum gap that should be maintained between the compressed
16 /// data's end address and the uncompressed data's end address when decompressing in-place.
17 /// When using the backwards compression mode the gap has to be between the start of the
18 /// compressed data and the start of the uncompressed data.
19 ///
20 /// Please refer to the original C implementation's
21 /// [readme](https://github.com/einar-saukas/ZX0#compressing-with-prefix) for an in-depth
22 /// explanation.
23 pub delta: usize
24}
25
26pub type ProgressCallback<'a> = Box<dyn FnMut(f32) + 'a>;
27
28/// This struct provides a means of initializing and performing a ZX0 compression operation by
29/// leveraging the builder pattern.
30///
31/// By calling [`Compressor::new`] a new [`Compressor`] will be instantiated using the following
32/// default values:
33///
34/// - No prefix/suffix skipping
35/// - Quick mode disabled
36/// - Backwards mode disabled
37/// - Classic mode disabled
38///
39/// After constructing a [`Compressor`] instance the method [`compress`](Compressor::compress) is available to compress
40/// `u8` slices. The [`Compressor`] can be resued again afterwards.
41///
42/// In contrast to the original C implementation, compression using the Rust ZX0 compressor is
43/// thread-safe, and can therefore be used to compress several slices in parallel.
44pub struct Compressor<'a> {
45 skip: usize,
46 quick_mode: bool,
47 backwards_mode: bool,
48 classic_mode: bool,
49 progress_callback: ProgressCallback<'a>
50}
51
52impl<'a> Compressor<'a> {
53 /// Instantiate a new [`Compressor`] using the following default values:
54 ///
55 /// - No prefix/suffix skipping
56 /// - Quick mode disabled
57 /// - Backwards mode disabled
58 /// - Classic mode disabled
59 pub fn new() -> Self {
60 Self {
61 skip: 0,
62 quick_mode: false,
63 backwards_mode: false,
64 classic_mode: false,
65 progress_callback: Box::new(|_| ())
66 }
67 }
68
69 /// Change the value for the quick mode setting. When enabled, this will cause the ZX0
70 /// compressor to use a smaller dictionary size, at the cost of a less efficient compression
71 /// ratio.
72 ///
73 /// Enabling this setting can be useful when producing debug assets where a short feedback loop
74 /// is more important than getting a good compression ratio.
75 pub fn quick_mode(&mut self, quick_mode: bool) -> &mut Self {
76 self.quick_mode = quick_mode;
77 self
78 }
79
80 /// Change the value for the backwards compression mode setting. This will cause the ZX0
81 /// compressor to create compressed data that should be decompressed back-to-front. This can be
82 /// useful in situations where in-place decompression is desired, and the end of the compressed
83 /// data overlaps with the end of the region that the uncompressed data should be positioned
84 /// in.
85 ///
86 /// Please refer to the original C implementation's
87 /// [readme](https://github.com/einar-saukas/ZX0#compressing-backwards) for an in-depth
88 /// explanation.
89 pub fn backwards_mode(&mut self, backwards_mode: bool) -> &mut Self {
90 self.backwards_mode = backwards_mode;
91 self
92 }
93
94 /// Change the value for the classic compression mode setting. Enabling this will cause the ZX0
95 /// compressor to output compressed data in its legacy V1 file format. This can be useful when
96 /// compressing for one of the platforms that only provides a V1 decompression routine.
97 pub fn classic_mode(&mut self, classic_mode: bool) -> &mut Self {
98 self.classic_mode = classic_mode;
99 self
100 }
101
102 /// Set a progress callback. When providing a closure to this function, that closure will be
103 /// called repeatedly during compression. The closure will be called with a progress value
104 /// between `0.0` and `1.0`. Note that due to the nature of the compression algorithm, this
105 /// value is not increasing linearly with time, and thus should be interpreted as a rough
106 /// estimate.
107 pub fn progress_callback<C: FnMut(f32) + 'a>(&mut self, progress_callback: C) -> &mut Self {
108 self.progress_callback = Box::new(progress_callback);
109 self
110 }
111
112 /// Set the number of prefix/suffix bytes to skip during compression. This will cause the
113 /// compressor to create a dictionary based on data that will already be in memory before the
114 /// compressed data during decompression. Of course, for this to work the prefix (or suffix in
115 /// case of backwards mode) in the file must be 100% identical to the data that is in memory
116 /// before or after the block of compressed data when attempting to decompress it.
117 ///
118 /// Please refer to the original C implementation's
119 /// [readme](https://github.com/einar-saukas/ZX0#compressing-with-prefix) for an in-depth
120 /// explanation.
121 pub fn skip(&mut self, skip: usize) -> &mut Self {
122 self.skip = skip;
123 self
124 }
125
126 /// Compress the provided slice.
127 ///
128 /// This returns a [`CompressionResult`] struct containing both the compressed data as well as
129 /// metadata related to the compression operation.
130 ///
131 /// The [`Compressor`] does not have to be discarded after calling this method. It does not
132 /// contain any state (only the configuration) and thus can be reused again for compressing
133 /// additional data.
134 pub fn compress(&mut self, input: &[u8]) -> CompressionResult {
135 let chain = {
136 let (allocator, mut optimal) = optimize(
137 input,
138 self.skip,
139 if self.quick_mode { MAX_OFFSET_ZX7 } else { MAX_OFFSET_ZX0 },
140 &mut self.progress_callback
141 );
142
143 let mut chain = Vec::new();
144
145 while optimal != 0 {
146 let oblock = allocator.get(optimal);
147
148 chain.push(Block {
149 bits: oblock.bits as u32,
150 index: oblock.index as isize,
151 offset: oblock.offset as usize
152 });
153
154 optimal = oblock.next_index;
155 }
156
157 chain
158 };
159
160 let invert_mode = !self.classic_mode && !self.backwards_mode;
161 let mut delta = 0;
162
163 let output = compress(
164 chain,
165 input,
166 self.skip,
167 self.backwards_mode,
168 invert_mode,
169 &mut delta
170 );
171
172 CompressionResult {
173 output,
174 delta
175 }
176 }
177}
178
179impl<'a> Default for Compressor<'a> {
180 fn default() -> Self {
181 Self::new()
182 }
183}