1use core::num::NonZeroUsize;
4
5use crate::{backend::BackendRequest, pixel::PixelFormat, scale::Downscale, types::Rect};
6
7mod allocation;
8mod collection;
9
10#[doc(hidden)]
11pub use allocation::{
12 checked_batch_count_product, checked_batch_count_sum, try_batch_reserve_for_push,
13 try_batch_reserve_to, BatchAllocationBudget, BatchAllocationRequest,
14};
15pub use collection::{
16 try_collect_indexed_batch_results, try_collect_ordered_batch_results,
17 try_collect_ordered_batch_results_with_limits,
18};
19
20#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
22pub struct TileBatchOptions {
23 pub workers: Option<NonZeroUsize>,
25}
26
27impl TileBatchOptions {
28 #[must_use]
30 pub const fn new(workers: Option<NonZeroUsize>) -> Self {
31 Self { workers }
32 }
33}
34
35#[doc(hidden)]
37pub type IndexedBatchResult<T, E> = (usize, Result<T, E>);
38
39#[doc(hidden)]
41pub type BatchResultSlot<T, E> = Option<Result<T, E>>;
42
43pub struct TileDecodeJob<'i, 'o> {
45 pub input: &'i [u8],
47 pub out: &'o mut [u8],
49 pub stride: usize,
51}
52
53pub struct TileRegionDecodeJob<'i, 'o> {
55 pub input: &'i [u8],
57 pub out: &'o mut [u8],
59 pub stride: usize,
61 pub roi: Rect,
63}
64
65pub struct TileScaledDecodeJob<'i, 'o> {
67 pub input: &'i [u8],
69 pub out: &'o mut [u8],
71 pub stride: usize,
73 pub scale: Downscale,
75}
76
77pub struct TileRegionScaledDecodeJob<'i, 'o> {
79 pub input: &'i [u8],
81 pub out: &'o mut [u8],
83 pub stride: usize,
85 pub roi: Rect,
87 pub scale: Downscale,
89}
90
91pub struct TileRegionScaledDeviceDecodeRequest<'i> {
93 pub input: &'i [u8],
95 pub fmt: PixelFormat,
97 pub roi: Rect,
99 pub scale: Downscale,
101 pub backend: BackendRequest,
103}
104
105#[derive(Debug)]
107pub struct TileBatchError<E> {
108 pub index: usize,
110 pub source: E,
112}
113
114impl<E: core::fmt::Display> core::fmt::Display for TileBatchError<E> {
115 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
116 write!(f, "tile {} decode failed: {}", self.index, self.source)
117 }
118}
119
120impl<E: core::error::Error + 'static> core::error::Error for TileBatchError<E> {
121 fn source(&self) -> Option<&(dyn core::error::Error + 'static)> {
122 Some(&self.source)
123 }
124}
125
126#[derive(Clone, Copy, Debug, Eq, PartialEq, thiserror::Error)]
131#[non_exhaustive]
132pub enum BatchInfrastructureError {
133 #[error("batch plan requires at least one job")]
135 EmptyBatchPlan,
136 #[error("{what} is too large: requested {requested} bytes, cap {cap}")]
138 AllocationTooLarge {
139 what: &'static str,
141 requested: usize,
143 cap: usize,
145 },
146 #[error("host allocation failed for {bytes} bytes while allocating {what}")]
148 HostAllocationFailed {
149 what: &'static str,
151 bytes: usize,
153 },
154 #[error("failed to spawn batch worker {worker}")]
156 WorkerSpawnFailed {
157 worker: usize,
159 },
160 #[error("batch worker {worker} panicked")]
162 WorkerPanicked {
163 worker: usize,
165 },
166 #[error("batch worker slot {worker} is outside retained slot count {available}")]
168 WorkerSlotMissing {
169 worker: usize,
171 available: usize,
173 },
174 #[error("parallel batch worker panicked")]
176 ParallelWorkerPanicked,
177 #[error("batch scheduler state was poisoned")]
179 SchedulerPoisoned,
180 #[error("batch result index {index} is outside job count {job_count}")]
182 ResultIndexOutOfBounds {
183 index: usize,
185 job_count: usize,
187 },
188 #[error("batch result index {index} was reported more than once")]
190 DuplicateResult {
191 index: usize,
193 },
194 #[error("batch worker result missing for job {index}")]
196 MissingResult {
197 index: usize,
199 },
200 #[error("batch result {index} changed kind during ordered collection")]
202 ResultKindMismatch {
203 index: usize,
205 },
206}
207
208#[derive(Debug)]
213#[non_exhaustive]
214pub enum BatchDecodeError<E> {
215 Tile(TileBatchError<E>),
217 Infrastructure(BatchInfrastructureError),
219}
220
221impl<E> BatchDecodeError<E> {
222 #[must_use]
224 pub const fn tile_error(&self) -> Option<&TileBatchError<E>> {
225 match self {
226 Self::Tile(error) => Some(error),
227 Self::Infrastructure(_) => None,
228 }
229 }
230
231 #[must_use]
233 pub const fn infrastructure_error(&self) -> Option<&BatchInfrastructureError> {
234 match self {
235 Self::Tile(_) => None,
236 Self::Infrastructure(error) => Some(error),
237 }
238 }
239}
240
241impl<E: core::fmt::Display> core::fmt::Display for BatchDecodeError<E> {
242 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
243 match self {
244 Self::Tile(error) => error.fmt(f),
245 Self::Infrastructure(error) => error.fmt(f),
246 }
247 }
248}
249
250impl<E: core::error::Error + 'static> core::error::Error for BatchDecodeError<E> {
251 fn source(&self) -> Option<&(dyn core::error::Error + 'static)> {
252 match self {
253 Self::Tile(error) => Some(error),
254 Self::Infrastructure(error) => Some(error),
255 }
256 }
257}
258
259impl<E> From<BatchInfrastructureError> for BatchDecodeError<E> {
260 fn from(error: BatchInfrastructureError) -> Self {
261 Self::Infrastructure(error)
262 }
263}
264
265impl<E> From<TileBatchError<E>> for BatchDecodeError<E> {
266 fn from(error: TileBatchError<E>) -> Self {
267 Self::Tile(error)
268 }
269}
270
271#[doc(hidden)]
276pub fn tile_batch_worker_count(
277 batch_size: usize,
278 options: TileBatchOptions,
279 available_workers: usize,
280) -> usize {
281 if batch_size <= 1 {
282 return 1;
283 }
284 let workers = options.workers.map_or(available_workers, NonZeroUsize::get);
285 workers.max(1).min(batch_size)
286}