1#![allow(
2 elided_lifetimes_in_paths,
3 clippy::needless_pass_by_value,
4 reason = "Bevy systems"
5)]
6
7use alloc::sync::Arc;
8use bevy_ecs::change_detection::DetectChangesMut;
9use core::{fmt, mem, ops};
10
11use bevy_ecs::prelude as ecs;
12use bevy_ecs::schedule::IntoScheduleConfigs as _;
13
14use crate::block::{self, Block, BlockChange, EvalBlockError, InEvalError, MinEval};
15use crate::listen::{self, Gate, IntoListener as _, Listener, Notifier};
16use crate::time;
17use crate::transaction::{self, Equal, Transaction};
18use crate::universe::{self, HandleVisitor, ReadTicket, VisitHandles};
19
20#[cfg(doc)]
21use crate::block::{EvaluatedBlock, Primitive};
22#[cfg(doc)]
23use crate::universe::Universe;
24
25type EvalResult = Result<MinEval, EvalBlockError>;
28
29#[derive(bevy_ecs::component::Component)]
40#[require(BlockDefNextValue)]
41pub struct BlockDef {
42 state: BlockDefState,
43
44 notifier: Arc<Notifier<BlockChange>>,
50}
51
52pub(crate) struct BlockDefState {
55 block: Block,
57
58 cache: EvalResult,
72
73 cache_dirty: listen::Flag,
75
76 listeners_ok: bool,
78
79 #[expect(unused, reason = "used only for its `Drop` behavior")]
81 block_listen_gate: Gate,
82}
83
84impl BlockDef {
85 pub fn new(read_ticket: ReadTicket<'_>, block: Block) -> Self {
88 BlockDef {
89 state: BlockDefState::new(block, read_ticket),
90 notifier: Arc::new(Notifier::new()),
91 }
92 }
93
94 pub fn block(&self) -> &Block {
100 &self.state.block
101 }
102
103 pub fn evaluate(
108 &self,
109 read_ticket: ReadTicket<'_>,
110 ) -> Result<block::EvaluatedBlock, EvalBlockError> {
111 let filter = block::EvalFilter::new(read_ticket);
112 block::finish_evaluation(
113 self.block().clone(),
114 filter.budget.get(),
115 {
116 block::Budget::decrement_components(&filter.budget).unwrap();
119
120 self.evaluate_impl(&filter)
121 },
122 &filter,
123 )
124 }
125
126 pub(super) fn evaluate_impl(
128 &self,
129 filter: &block::EvalFilter<'_>,
130 ) -> Result<MinEval, InEvalError> {
131 let &block::EvalFilter {
132 read_ticket: _,
133 skip_eval,
134 ref listener,
135 budget: _, } = filter;
137
138 if let Some(listener) = listener {
139 <BlockDef as listen::Listen>::listen(self, listener.clone());
140 }
141
142 if skip_eval {
143 Ok(block::AIR_EVALUATED_MIN)
146 } else {
147 self.state
150 .cache
151 .clone()
152 .map_err(EvalBlockError::into_internal_error_for_block_def)
153 }
154 }
155}
156
157impl BlockDefState {
158 #[inline]
159 fn new(block: Block, read_ticket: ReadTicket<'_>) -> Self {
160 let cache_dirty = listen::Flag::new(false);
161 let (block_listen_gate, block_listener) =
162 Listener::<BlockChange>::gate(cache_dirty.listener());
163
164 let cache = block
165 .evaluate2(&block::EvalFilter {
166 read_ticket,
167 skip_eval: false,
168 listener: Some(block_listener.into_listener()),
169 budget: Default::default(),
170 })
171 .map(MinEval::from);
172
173 BlockDefState {
174 listeners_ok: cache.is_ok(),
175
176 block,
177 cache,
178 cache_dirty,
179 block_listen_gate,
180 }
181 }
182}
183
184impl fmt::Debug for BlockDef {
185 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
186 let Self {
188 state:
189 BlockDefState {
190 block,
191 cache: _,
192 cache_dirty,
193 listeners_ok,
194 block_listen_gate: _,
195 },
196 notifier,
197 } = self;
198 f.debug_struct("BlockDef")
199 .field("block", &block)
200 .field("cache_dirty", &cache_dirty)
201 .field("listeners_ok", &listeners_ok)
202 .field("notifier", ¬ifier)
203 .finish_non_exhaustive()
204 }
205}
206
207impl listen::Listen for BlockDef {
210 type Msg = BlockChange;
211 type Listener = <Notifier<Self::Msg> as listen::Listen>::Listener;
212
213 fn listen_raw(&self, listener: Self::Listener) {
214 self.notifier.listen_raw(listener)
215 }
216}
217
218impl AsRef<Block> for BlockDef {
219 fn as_ref(&self) -> &Block {
220 &self.state.block
221 }
222}
223
224impl VisitHandles for BlockDef {
225 fn visit_handles(&self, visitor: &mut dyn HandleVisitor) {
226 let Self {
227 state:
228 BlockDefState {
229 block,
230 cache: _,
233 cache_dirty: _,
234 listeners_ok: _,
235 block_listen_gate: _,
236 },
237 notifier: _,
238 } = self;
239 block.visit_handles(visitor);
240 }
241}
242
243universe::impl_universe_member_for_single_component_type!(BlockDef);
244
245impl transaction::Transactional for BlockDef {
246 type Transaction = BlockDefTransaction;
247}
248
249#[cfg(feature = "arbitrary")]
250impl<'a> arbitrary::Arbitrary<'a> for BlockDef {
251 fn arbitrary(u: &mut arbitrary::Unstructured<'a>) -> arbitrary::Result<Self> {
252 Ok(BlockDef::new(ReadTicket::stub(), Block::arbitrary(u)?))
253 }
254
255 fn size_hint(depth: usize) -> (usize, Option<usize>) {
256 Block::size_hint(depth)
258 }
259}
260
261#[derive(Clone, Debug, Default, Eq, Hash, PartialEq)]
263#[must_use]
264pub struct BlockDefTransaction {
265 old: Equal<Block>,
268 new: Equal<Block>,
270}
271
272impl BlockDefTransaction {
273 pub fn expect(old: Block) -> Self {
276 Self {
277 old: Equal(Some(old)),
278 new: Equal(None),
279 }
280 }
281
282 pub fn overwrite(new: Block) -> Self {
284 Self {
285 old: Equal(None),
286 new: Equal(Some(new)),
287 }
288 }
289
290 pub fn replace(old: Block, new: Block) -> Self {
293 Self {
294 old: Equal(Some(old)),
295 new: Equal(Some(new)),
296 }
297 }
298}
299
300#[expect(missing_debug_implementations)]
301#[doc(hidden)] pub struct Check {
303 new_state: Option<BlockDefState>,
305}
306
307impl Transaction for BlockDefTransaction {
308 type Target = BlockDef;
309 type Context<'a> = ReadTicket<'a>;
310 type CommitCheck = Check;
311 type Output = transaction::NoOutput;
312 type Mismatch = BlockDefMismatch;
313
314 fn check(
315 &self,
316 target: &BlockDef,
317 read_ticket: Self::Context<'_>,
318 ) -> Result<Self::CommitCheck, Self::Mismatch> {
319 self.old.check(&target.state.block).map_err(|_| BlockDefMismatch::Unexpected)?;
321
322 Ok(Check {
329 new_state: self.new.0.clone().map(|block| BlockDefState::new(block, read_ticket)),
330 })
331 }
332
333 fn commit(
334 self,
335 target: &mut BlockDef,
336 check: Self::CommitCheck,
337 _outputs: &mut dyn FnMut(Self::Output),
338 ) -> Result<(), transaction::CommitError> {
339 match (self.new, check.new_state) {
340 (Equal(Some(_)), Some(new_state)) => {
341 target.state = new_state;
342 target.notifier.notify(&BlockChange::new());
343 }
344 (Equal(None), None) => {}
345 _ => panic!("BlockDefTransaction check value is inconsistent"),
346 }
347 Ok(())
348 }
349}
350
351impl universe::TransactionOnEcs for BlockDefTransaction {
352 type WriteQueryData = &'static mut Self::Target;
353
354 fn check(
355 &self,
356 target: &BlockDef,
357 read_ticket: ReadTicket<'_>,
358 ) -> Result<Self::CommitCheck, Self::Mismatch> {
359 Transaction::check(self, target, read_ticket)
360 }
361
362 fn commit(
363 self,
364 mut target: ecs::Mut<'_, BlockDef>,
365 check: Self::CommitCheck,
366 ) -> Result<(), transaction::CommitError> {
367 Transaction::commit(self, &mut *target, check, &mut transaction::no_outputs)
368 }
369}
370
371impl transaction::Merge for BlockDefTransaction {
372 type MergeCheck = ();
373 type Conflict = BlockDefConflict;
374
375 fn check_merge(&self, other: &Self) -> Result<Self::MergeCheck, Self::Conflict> {
376 let conflict = BlockDefConflict {
377 old: self.old.check_merge(&other.old).is_err(),
378 new: self.new.check_merge(&other.new).is_err(),
379 };
380
381 if (conflict
382 != BlockDefConflict {
383 old: false,
384 new: false,
385 })
386 {
387 Err(conflict)
388 } else {
389 Ok(())
390 }
391 }
392
393 fn commit_merge(&mut self, other: Self, (): Self::MergeCheck) {
394 let Self { old, new } = self;
395 old.commit_merge(other.old, ());
396 new.commit_merge(other.new, ());
397 }
398}
399
400#[derive(Clone, Debug, Eq, PartialEq, displaydoc::Display)]
402#[non_exhaustive]
403pub enum BlockDefMismatch {
404 Unexpected,
406}
407
408#[derive(Clone, Copy, Debug, Eq, PartialEq)]
412#[non_exhaustive]
413pub struct BlockDefConflict {
414 pub(crate) old: bool,
416 pub(crate) new: bool,
418}
419
420impl core::error::Error for BlockDefMismatch {}
421impl core::error::Error for BlockDefConflict {}
422
423impl fmt::Display for BlockDefConflict {
424 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
425 match *self {
426 BlockDefConflict {
427 old: true,
428 new: false,
429 } => write!(f, "different preconditions for BlockDef"),
430 BlockDefConflict {
431 old: false,
432 new: true,
433 } => write!(f, "cannot write different blocks to the same BlockDef"),
434 BlockDefConflict {
435 old: true,
436 new: true,
437 } => write!(f, "different preconditions (with write)"),
438 BlockDefConflict {
439 old: false,
440 new: false,
441 } => unreachable!(),
442 }
443 }
444}
445
446#[derive(Clone, Copy, Debug, Default, PartialEq)]
447pub(crate) struct BlockDefStepInfo {
448 attempted: usize,
450 updated: usize,
452 was_in_use: usize,
454}
455
456impl ops::Add for BlockDefStepInfo {
457 type Output = Self;
458 #[inline]
459 fn add(self, rhs: Self) -> Self::Output {
460 Self {
461 attempted: self.attempted + rhs.attempted,
462 updated: self.updated + rhs.updated,
463 was_in_use: self.was_in_use + rhs.was_in_use,
464 }
465 }
466}
467
468impl ops::AddAssign for BlockDefStepInfo {
469 #[inline]
470 fn add_assign(&mut self, other: Self) {
471 *self = *self + other;
472 }
473}
474
475impl manyfmt::Fmt<crate::util::StatusText> for BlockDefStepInfo {
476 fn fmt(&self, fmt: &mut fmt::Formatter<'_>, _: &crate::util::StatusText) -> fmt::Result {
477 let Self {
478 attempted,
479 updated,
480 was_in_use,
481 } = self;
482 write!(
483 fmt,
484 "{attempted} attempted, {updated} updated, {was_in_use} were in use"
485 )
486 }
487}
488
489#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq, bevy_ecs::schedule::SystemSet)]
492pub(crate) struct BlockDefUpdateSet;
493
494pub(crate) fn add_block_def_systems(world: &mut ecs::World) {
496 let mut schedules = world.resource_mut::<ecs::Schedules>();
497 schedules.add_systems(
498 time::schedule::Synchronize,
499 (update_phase_1, update_phase_2).chain().in_set(BlockDefUpdateSet),
500 );
501}
502
503#[derive(ecs::Component, Default)]
507pub(crate) enum BlockDefNextValue {
509 #[default]
510 None,
511 NewEvaluation(EvalResult),
512 NewState(BlockDefState),
514}
515
516#[allow(clippy::needless_pass_by_value)]
519pub(crate) fn update_phase_1(
520 mut info_collector: ecs::ResMut<universe::InfoCollector<BlockDefStepInfo>>,
521 mut defs: ecs::Query<(&BlockDef, &mut BlockDefNextValue)>,
522 data_sources: universe::QueryBlockDataSources,
523) {
524 let mq = data_sources.get();
525 let read_ticket = ReadTicket::from_queries(&mq);
526 let mut info = BlockDefStepInfo::default();
527 for (def, mut next) in defs.iter_mut() {
529 debug_assert!(
530 matches!(*next, BlockDefNextValue::None),
531 "BlockDefNextValue should have been cleared",
532 );
533
534 if !def.state.listeners_ok {
535 info.attempted += 1;
536 *next = BlockDefNextValue::NewState(BlockDefState::new(
539 def.state.block.clone(),
540 read_ticket,
541 ));
542 info.updated += 1;
543 } else if def.state.cache_dirty.get_and_clear() {
544 info.attempted += 1;
547
548 let new_cache = def
549 .state
550 .block
551 .evaluate2(&block::EvalFilter {
552 read_ticket,
553 skip_eval: false,
554 listener: None, budget: Default::default(),
556 })
557 .map(MinEval::from);
558
559 if !matches!(new_cache, Err(ref e) if e.is_transient()) && new_cache != def.state.cache
561 {
562 *next = BlockDefNextValue::NewEvaluation(new_cache);
563 info.updated += 1;
564 }
565 }
566
567 if info.attempted > 0 && matches!(def.state.cache, Err(ref e) if e.is_transient()) {
568 info.was_in_use += 1;
569 }
570 }
571 info_collector.record(info);
572}
573
574pub(crate) fn update_phase_2(
579 mut defs: ecs::Query<
580 '_,
581 '_,
582 (&mut BlockDef, &mut BlockDefNextValue),
583 ecs::Changed<BlockDefNextValue>,
584 >,
585) {
586 defs.par_iter_mut().for_each(|(mut def, mut next)| {
587 match mem::take(next.bypass_change_detection()) {
591 BlockDefNextValue::NewEvaluation(result) => {
592 def.state.cache = result;
593 def.notifier.notify(&BlockChange::new());
594 }
595 BlockDefNextValue::NewState(result) => {
596 def.state = result;
597 def.notifier.notify(&BlockChange::new());
598 }
599 BlockDefNextValue::None => {}
600 }
601 });
602}
603
604#[cfg(test)]
607mod tests {
608 use super::*;
609 use crate::math::Rgba;
610 use crate::universe::Universe;
611 use pretty_assertions::assert_eq;
612
613 #[test]
618 fn evaluate_equivalence() {
619 let mut universe = Universe::new();
620 let block = Block::builder().color(Rgba::new(1.0, 0.0, 0.0, 1.0)).build();
621
622 let eval_bare = block.evaluate(universe.read_ticket()).unwrap();
623 let block_def = BlockDef::new(universe.read_ticket(), block.clone());
624 let eval_def = block_def.evaluate(universe.read_ticket()).unwrap();
625 let block_def_handle = universe.insert_anonymous(block_def);
626 let indirect_block = Block::from(block_def_handle);
627 let eval_indirect = indirect_block.evaluate(universe.read_ticket()).unwrap();
628
629 assert_eq!(
630 block::EvaluatedBlock {
631 block: indirect_block,
632 ..eval_def.clone()
633 },
634 eval_indirect,
635 "BlockDef::evaluate() same except for block as Primitive::Indirect"
636 );
637 assert_eq!(
638 block::EvaluatedBlock {
639 block,
640 cost: eval_bare.cost,
641 ..eval_def
642 },
643 eval_bare,
644 "BlockDef::evaluate() same except for block and cost as the def block"
645 );
646 }
647}