1use core::cell::Cell;
5use core::fmt;
6
7use crate::block::{
8 self, Block, BlockAttributes, BlockChange, EvaluatedBlock, Evoxel, Evoxels, Resolution,
9};
10use crate::content::palette;
11use crate::listen;
12use crate::math::{GridAab, Rgba, Vol};
13use crate::universe::{HandleError, ReadTicket};
14
15#[cfg(doc)]
16use crate::{block::BlockDef, space::Space, universe::Handle};
17
18#[derive(Clone, Debug)]
20pub(crate) struct EvalFilter<'a> {
21 pub read_ticket: ReadTicket<'a>,
24
25 pub skip_eval: bool,
31
32 pub listener: Option<listen::DynListener<BlockChange>>,
41
42 pub budget: Cell<Budget>,
48}
49
50impl<'a> EvalFilter<'a> {
51 pub fn new(read_ticket: ReadTicket<'a>) -> Self {
54 Self {
55 read_ticket,
56 skip_eval: Default::default(),
57 listener: Default::default(),
58 budget: Default::default(),
59 }
60 }
61}
62
63#[derive(Clone, Copy, Debug, Eq, PartialEq)]
70pub(crate) struct Budget {
71 pub(crate) components: u32,
73
74 pub(crate) voxels: u32,
77
78 pub(crate) recursion: u8,
86
87 pub(crate) min_recursion: u8,
92}
93
94impl Budget {
95 pub(in crate::block) fn decrement_components(cell: &Cell<Budget>) -> Result<(), InEvalError> {
96 let mut budget = cell.get();
97 match budget.components.checked_sub(1) {
98 Some(updated) => budget.components = updated,
99 None => return Err(InEvalError::BudgetExceeded),
100 }
101 cell.set(budget);
102 Ok(())
103 }
104
105 pub(in crate::block) fn decrement_voxels(
106 cell: &Cell<Budget>,
107 amount: usize,
108 ) -> Result<(), InEvalError> {
109 let mut budget = cell.get();
110 match u32::try_from(amount).ok().and_then(|amount| budget.voxels.checked_sub(amount)) {
111 Some(updated) => budget.voxels = updated,
112 None => return Err(InEvalError::BudgetExceeded),
113 }
114 cell.set(budget);
115 Ok(())
116 }
117
118 pub(in crate::block) fn recurse(
119 cell: &Cell<Budget>,
120 ) -> Result<BudgetRecurseGuard<'_>, InEvalError> {
121 let current = cell.get();
122 let mut recursed = current;
123 match recursed.recursion.checked_sub(1) {
124 Some(updated) => {
125 recursed.recursion = updated;
126 recursed.min_recursion = recursed.min_recursion.min(updated);
127 }
128 None => return Err(InEvalError::BudgetExceeded),
129 }
130 cell.set(recursed);
131 Ok(BudgetRecurseGuard { cell })
132 }
133
134 pub(crate) fn to_cost(self) -> Cost {
141 assert_eq!(
142 self.recursion, self.min_recursion,
143 "do not use `to_cost()` on used budgets"
144 );
145 Cost {
146 components: self.components,
147 voxels: self.voxels,
148 recursion: self.recursion,
149 }
150 }
151}
152
153impl Default for Budget {
154 fn default() -> Self {
156 let recursion = 30;
157 Self {
158 components: 1000,
159 voxels: 64 * 64 * 128,
160 recursion,
161 min_recursion: recursion,
162 }
163 }
164}
165
166#[must_use]
167pub(crate) struct BudgetRecurseGuard<'a> {
168 cell: &'a Cell<Budget>,
169}
170
171impl Drop for BudgetRecurseGuard<'_> {
172 fn drop(&mut self) {
173 let mut budget = self.cell.get();
174 budget.recursion = budget.recursion.strict_add(1);
175 self.cell.set(budget);
176 }
177}
178
179#[derive(Clone, Copy, Debug, Hash, Eq, PartialEq)]
185#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
186pub struct Cost {
187 pub(crate) components: u32,
189
190 pub(crate) voxels: u32,
193
194 pub(crate) recursion: u8,
200}
201
202impl Cost {
203 pub const ZERO: Self = {
205 Self {
206 components: 0,
207 voxels: 0,
208 recursion: 0,
209 }
210 };
211
212 pub(crate) fn from_difference(original_budget: Budget, final_budget: Budget) -> Self {
214 let Some(new_self) = (|| {
215 Some(Self {
216 components: original_budget.components.checked_sub(final_budget.components)?,
217 voxels: original_budget.voxels.checked_sub(final_budget.voxels)?,
218 recursion: original_budget.recursion.checked_sub(final_budget.min_recursion)?,
220 })
221 })() else {
222 panic!("overflow computing budget difference: {final_budget:#?} - {original_budget:#?}")
223 };
224 new_self
225 }
226}
227
228#[derive(Clone, Debug, Eq, Hash, PartialEq)]
230pub struct EvalBlockError {
231 pub(crate) block: Block,
233
234 pub(crate) budget: Cost,
239
240 pub(crate) used: Cost,
242
243 pub(crate) kind: ErrorKind,
245}
246
247#[derive(Clone, Debug, Eq, Hash, PartialEq)]
248#[non_exhaustive]
249pub(crate) enum ErrorKind {
251 BudgetExceeded,
253
254 PriorBudgetExceeded {
258 budget: Cost,
260 used: Cost,
262 },
263
264 Handle(HandleError),
268}
269
270#[derive(Debug)]
275pub(in crate::block) enum InEvalError {
276 BudgetExceeded,
277 PriorBudgetExceeded { budget: Cost, used: Cost },
278 Handle(HandleError),
279}
280
281impl fmt::Display for EvalBlockError {
282 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
283 match self.kind {
284 ErrorKind::BudgetExceeded => {
285 let Self { budget, used, .. } = self;
286 write!(
287 f,
288 "block definition exceeded evaluation budget; \
289 used {used:?} so far and only {budget:?} available"
290 )
291 }
292 ErrorKind::PriorBudgetExceeded { budget, used } => write!(
293 f,
294 "cached block definition exceeded evaluation budget; \
295 used {used:?} so far and only {budget:?} available"
296 ),
297 ErrorKind::Handle(_) => write!(f, "block data inaccessible"),
298 }
299 }
300}
301
302impl core::error::Error for EvalBlockError {
303 fn source(&self) -> Option<&(dyn core::error::Error + 'static)> {
304 match &self.kind {
305 ErrorKind::BudgetExceeded => None,
306 ErrorKind::PriorBudgetExceeded { .. } => None,
307 ErrorKind::Handle(e) => Some(e),
308 }
309 }
310}
311
312impl From<HandleError> for InEvalError {
313 fn from(value: HandleError) -> Self {
314 InEvalError::Handle(value)
315 }
316}
317
318impl InEvalError {
319 pub(in crate::block) fn into_eval_error(
320 self,
321 block: Block,
322 budget: Cost,
323 used: Cost,
324 ) -> EvalBlockError {
325 EvalBlockError {
326 block,
327 budget,
328 used,
329 kind: match self {
330 InEvalError::BudgetExceeded => ErrorKind::BudgetExceeded,
331 #[expect(clippy::shadow_unrelated)]
332 InEvalError::PriorBudgetExceeded { budget, used } => {
333 ErrorKind::PriorBudgetExceeded { budget, used }
334 }
335 InEvalError::Handle(e) => ErrorKind::Handle(e),
336 },
337 }
338 }
339}
340
341impl EvalBlockError {
342 pub fn block(&self) -> &Block {
344 &self.block
345 }
346
347 pub(in crate::block) fn into_internal_error_for_block_def(self) -> InEvalError {
348 match self.kind {
349 ErrorKind::PriorBudgetExceeded { budget, used } => {
350 InEvalError::PriorBudgetExceeded { budget, used }
351 }
352 ErrorKind::BudgetExceeded => InEvalError::BudgetExceeded,
353 ErrorKind::Handle(e) => InEvalError::Handle(e),
354 }
355 }
356
357 #[doc(hidden)] pub fn is_wrong_universe(&self) -> bool {
359 matches!(
360 self.kind,
361 ErrorKind::Handle(ref e) if e.is_wrong_universe()
362 )
363 }
364
365 pub(crate) fn is_transient(&self) -> bool {
370 match self.kind {
371 ErrorKind::Handle(ref h) => h.is_transient(),
372 ErrorKind::BudgetExceeded => false,
373 ErrorKind::PriorBudgetExceeded { .. } => false,
374 }
375 }
376
377 pub fn to_placeholder(&self) -> EvaluatedBlock {
383 let resolution = Resolution::R8;
384 let pattern = [palette::BLOCK_EVAL_ERROR, Rgba::BLACK].map(Evoxel::from_color);
386
387 EvaluatedBlock::from_voxels(
388 block::AIR, BlockAttributes {
390 display_name: format!("Block error: {self}").into(),
391 selectable: false, ..Default::default()
393 },
394 Evoxels::from_many(
395 resolution,
396 Vol::from_fn(GridAab::for_block(resolution), |cube| {
397 pattern[((cube.x + cube.y + cube.z).rem_euclid(2)) as usize]
398 }),
399 ),
400 self.used,
401 )
402 }
403}
404
405pub(in crate::block) fn finish_evaluation(
411 block: Block,
412 original_budget: Budget,
413 result: Result<block::MinEval, InEvalError>,
414 filter: &EvalFilter<'_>,
415) -> Result<EvaluatedBlock, EvalBlockError> {
416 let cost = Cost::from_difference(original_budget, filter.budget.get());
417 match result {
418 Ok(ev) => Ok(ev.finish(block, cost)),
419 Err(err) => Err(err.into_eval_error(block, original_budget.to_cost(), cost)),
420 }
421}
422
423#[cfg(test)]
424mod tests {
425 use super::*;
426
427 #[test]
430 fn tracking_recursion_cost() {
431 let cell = Cell::new(Budget::default());
432 assert_eq!((cell.get().recursion, cell.get().min_recursion), (30, 30));
433
434 {
435 let _guard1: BudgetRecurseGuard<'_> = Budget::recurse(&cell).unwrap();
436 assert_eq!((cell.get().recursion, cell.get().min_recursion), (29, 29));
437 {
438 let _guard2: BudgetRecurseGuard<'_> = Budget::recurse(&cell).unwrap();
439 assert_eq!((cell.get().recursion, cell.get().min_recursion), (28, 28));
440 }
441 }
442 assert_eq!((cell.get().recursion, cell.get().min_recursion), (30, 28));
443
444 let cost = Cost::from_difference(Budget::default(), cell.get());
445 assert_eq!(cost.recursion, 2);
446 }
447}