all_is_cubes/
behavior.rs

1//! Dynamic add-ons to game objects; we might also have called them “components”.
2
3use alloc::collections::{BTreeMap, BTreeSet};
4use alloc::sync::{Arc, Weak};
5use alloc::vec::Vec;
6use core::any::{Any, TypeId};
7use core::fmt;
8use core::mem;
9use core::ops;
10use core::task::Waker;
11use core::time::Duration;
12use manyfmt::Refmt as _;
13
14use bevy_ecs::prelude as ecs;
15use bevy_platform::{sync::Mutex, time::Instant};
16
17use crate::time::Tick;
18use crate::transaction::{self, Merge as _, Transaction};
19use crate::universe::{self, HandleVisitor, ReadTicket, UniverseTransaction, VisitHandles};
20
21#[cfg(doc)]
22use crate::universe::Universe;
23use crate::util::StatusText;
24
25/// Dynamic add-ons to game objects; we might also have called them “components”.
26/// Each behavior is owned by a “host” of type `H` which determines when the behavior
27/// is invoked.
28pub trait Behavior<H: Host>: fmt::Debug + Any + Send + Sync + VisitHandles + 'static {
29    /// Computes a transaction to apply the effects of this behavior for one timestep,
30    /// and specifies when next to step the behavior again (if ever).
31    ///
32    /// TODO: Define what happens if the transaction fails.
33    fn step(&self, context: &Context<'_, '_, H>) -> (UniverseTransaction, Then);
34
35    /// If `None`, then the behavior should not be persisted/saved to disk, because it will be
36    /// reconstructed as needed (e.g. collision, occupancy, user interaction, particles).
37    ///
38    /// If `Some`, then the representation that should be serialized, which must specify not
39    /// just the state of the behavior but _which_ behavior to recreate.
40    ///
41    /// TODO: Return type isn't a clean public API, nor extensible.
42    fn persistence(&self) -> Option<Persistence>;
43}
44
45/// A type that can have attached behaviors.
46pub trait Host: universe::UniverseMember + transaction::Transactional + 'static {
47    /// Additional data about “where” the behavior is attached to the host; what part of
48    /// the host should be affected by the behavior.
49    type Attachment: fmt::Debug + Clone + Eq + 'static;
50}
51
52/// Items available to a [`Behavior`] during [`Behavior::step()`].
53///
54// Design note: The sole reason this struct has two lifetimes is that in Rust, trait associated
55// types are always invariant, so we cannot have `&'ctx H::Read<'ctx>`.`
56#[non_exhaustive]
57pub struct Context<'ctx, 'read, H: Host> {
58    /// The time tick that is currently passing, causing this step.
59    pub tick: Tick,
60
61    /// [`ReadTicket`] for the universe this behavior is contained in.
62    pub read_ticket: ReadTicket<'ctx>,
63
64    /// The current state of the behavior's host object.
65    pub host: &'ctx H::Read<'read>,
66
67    /// Additional data about “where” the behavior is attached to the host; what part of
68    /// the host should be affected by the behavior.
69    pub attachment: &'ctx H::Attachment,
70
71    waker: &'ctx Waker,
72
73    host_transaction_binder: &'ctx dyn Fn(H::Transaction) -> UniverseTransaction,
74    self_transaction_binder: &'ctx dyn Fn(Arc<dyn Behavior<H>>) -> UniverseTransaction,
75}
76
77impl<'a, H: Host> Context<'a, '_, H> {
78    /// Returns a waker that should be used to signal when the behavior's
79    /// [`step()`](Behavior::step) should be called again, in the case where it
80    /// returns [`Then::Sleep`].
81    ///
82    /// This is precisely analogous to the use of [`Waker`] with [`Future::poll()`].
83    pub fn waker(&self) -> &'a Waker {
84        self.waker
85    }
86
87    /// Take a transaction applicable to the behavior's host, and wrap it to become a
88    /// [`UniverseTransaction`] for the host's containing universe.
89    pub fn bind_host(&self, transaction: H::Transaction) -> UniverseTransaction {
90        (self.host_transaction_binder)(transaction)
91    }
92
93    /// Returns a transaction which will replace this behavior with a new value.
94    ///
95    /// This should be used whenever a behavior wishes to modify itself, to ensure that
96    /// the modification only takes effect when the behavior's other effects do.
97    pub fn replace_self<B: Behavior<H> + 'static>(&self, new_behavior: B) -> UniverseTransaction {
98        (self.self_transaction_binder)(Arc::new(new_behavior))
99    }
100}
101
102impl<H: Host + fmt::Debug> fmt::Debug for Context<'_, '_, H> {
103    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
104        // binder functions are not debuggable
105        f.debug_struct("Context").finish_non_exhaustive()
106    }
107}
108
109/// A behavior's request for what should happen to it next.
110///
111/// Returned from [`Behavior::step()`]. Analogous to [`core::task::Poll`] for futures.
112#[derive(Debug, Clone, PartialEq)]
113#[non_exhaustive]
114pub enum Then {
115    /// Remove the behavior from the behavior set, and never call it again.
116    Drop,
117
118    /// Step again upon the next tick.
119    // TODO: specify whether to step when paused?
120    Step,
121
122    /// Don't step until the [`Context::waker()`] is invoked.
123    Sleep,
124}
125
126/// Collects [`Behavior`]s and invokes them.
127///
128/// Note: This type is public out of necessity because it is revealed elsewhere, but its details
129/// are currently subject to change.
130///
131/// To modify the set, use a [`BehaviorSetTransaction`].
132///
133#[doc = include_str!("save/serde-warning.md")]
134#[expect(clippy::module_name_repetitions)] // TODO: rename to Set?
135#[derive(ecs::Component)]
136pub struct BehaviorSet<H: Host> {
137    /// Note that this map is deterministically ordered, so any incidental things
138    /// depending on ordering, such as [`Self::query()`] will be deterministic.
139    /// (Transaction merges would prevent nondeterministic gameplay outcomes, but
140    /// it still wouldn't be ideal.)
141    members: BTreeMap<Key, BehaviorSetEntry<H>>,
142
143    /// Contains the key of every behavior whose waker was invoked.
144    woken: Arc<WokenSet>,
145}
146
147impl<H: Host> BehaviorSet<H> {
148    /// Constructs an empty [`BehaviorSet`].
149    pub fn new() -> Self {
150        BehaviorSet {
151            members: BTreeMap::new(),
152            woken: Default::default(),
153        }
154    }
155
156    /// Find behaviors of a specified type.
157    ///
158    /// The behaviors will be returned in a deterministic order. In the current
159    /// implementation, that order is the order in which they were added.
160    ///
161    /// TODO: Allow querying by attachment details (spatial, etc)
162    pub fn query<T: Behavior<H>>(&self) -> impl Iterator<Item = QueryItem<'_, H, T>> + '_ {
163        self.query_any(Some(TypeId::of::<T>())).map(
164            |QueryItem {
165                 attachment,
166                 behavior,
167             }| QueryItem {
168                attachment,
169                behavior: <dyn Any>::downcast_ref::<T>(behavior).unwrap(),
170            },
171        )
172    }
173
174    /// Find behaviors by filter criteria. All `None`s mean “anything”.
175    ///
176    /// The behaviors will be returned in a deterministic order. In the current
177    /// implementation, that order is the order in which they were added.
178    ///
179    /// TODO: Allow querying by attachment details (spatial, etc)
180    pub fn query_any<'a>(
181        &'a self,
182        type_filter: Option<TypeId>,
183    ) -> impl Iterator<Item = QueryItem<'a, H, dyn Behavior<H> + 'static>> + 'a {
184        self.members
185            .values()
186            .map(
187                move |entry: &'a BehaviorSetEntry<H>| -> QueryItem<'a, H, dyn Behavior<H> + 'static> {
188                    QueryItem {
189                        attachment: &entry.attachment,
190                        behavior: &*entry.behavior,
191                    }
192                },
193            )
194            .filter(move |qi| type_filter.is_none_or(|t| (*qi.behavior).type_id() == t))
195    }
196
197    pub(crate) fn step(
198        &self,
199        read_ticket: ReadTicket<'_>,
200        host: &H::Read<'_>,
201        host_transaction_binder: &dyn Fn(H::Transaction) -> UniverseTransaction,
202        // This is not `dyn` because it doesn't need to be stored, and there's no advantage
203        // to monomorphizing because this function is only going to be called once per `H`
204        // most of the time.
205        set_transaction_binder: impl Fn(BehaviorSetTransaction<H>) -> H::Transaction,
206        tick: Tick,
207    ) -> (UniverseTransaction, BehaviorSetStepInfo) {
208        let mut transactions = Vec::new();
209        let mut info = BehaviorSetStepInfo {
210            stepped: 0,
211            acted: 0,
212            total_count: self.members.len(),
213            total_time: Duration::ZERO, // placeholder
214        };
215        let start_time = Instant::now();
216
217        // TODO: Find a way to drain the set without holding the lock and without
218        // reallocating.
219        let woken: BTreeSet<_> = mem::take(&mut self.woken.lock().unwrap());
220
221        for key in woken {
222            let Some(entry) = self.members.get(&key) else {
223                // ignore spurious wakes of dropped behaviors
224                continue;
225            };
226
227            let context = &Context {
228                tick,
229                read_ticket,
230                host,
231                attachment: &entry.attachment,
232                waker: entry.waker.as_ref().unwrap(),
233                host_transaction_binder,
234                self_transaction_binder: &|new_behavior| {
235                    host_transaction_binder(set_transaction_binder(
236                        BehaviorSetTransaction::replace(
237                            key,
238                            Replace {
239                                old: entry.clone(),
240                                new: Some(BehaviorSetEntry {
241                                    attachment: entry.attachment.clone(),
242                                    behavior: new_behavior,
243                                    waker: None,
244                                }),
245                            },
246                        ),
247                    ))
248                },
249            };
250            info.stepped += 1;
251            let (txn, then) = entry.behavior.step(context);
252            if !txn.is_empty() {
253                info.acted += 1;
254                transactions.push(txn);
255            }
256            match then {
257                Then::Drop => transactions.push(host_transaction_binder(set_transaction_binder(
258                    BehaviorSetTransaction::delete(key, entry.clone()),
259                ))),
260
261                // Step is currently equivalent to just self-waking immediately.
262                Then::Step => context.waker.wake_by_ref(),
263
264                Then::Sleep => { /* no action needed */ }
265            }
266        }
267        let transaction = transactions
268            .into_iter()
269            .reduce(|a, b| a.merge(b).expect("TODO: handle merge failure"))
270            .unwrap_or_default();
271
272        info.total_time = start_time.elapsed();
273        (transaction, info)
274    }
275
276    #[allow(unused)] // currently only used on feature=save
277    pub(crate) fn iter(&self) -> impl Iterator<Item = &BehaviorSetEntry<H>> + '_ {
278        self.members.values()
279    }
280
281    #[allow(unused)] // currently only used on feature=save
282    pub(crate) fn is_empty(&self) -> bool {
283        self.members.is_empty()
284    }
285}
286
287impl<H: Host> Clone for BehaviorSet<H> {
288    fn clone(&self) -> Self {
289        let woken = Arc::new(Mutex::new(self.members.keys().copied().collect()));
290
291        // Reassign keys and wakers to be unique
292        // Note: This is similar to `BehaviorSetTransaction::commit()`.
293        let members: BTreeMap<Key, _> = self
294            .members
295            .values()
296            .map(|entry| {
297                let mut entry = entry.clone();
298                let key = Key::new();
299                entry.waker = Some(BehaviorWakerInner::create_waker(key, &woken));
300                (key, entry)
301            })
302            .collect();
303
304        Self { members, woken }
305    }
306}
307
308impl<H: Host> Default for BehaviorSet<H> {
309    fn default() -> Self {
310        Self::new()
311    }
312}
313
314impl<H: Host> fmt::Debug for BehaviorSet<H> {
315    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
316        write!(f, "BehaviorSet(")?;
317        f.debug_map().entries(self.members.iter()).finish()?;
318        write!(f, ")")?;
319        Ok(())
320    }
321}
322
323impl<H: Host> VisitHandles for BehaviorSet<H> {
324    fn visit_handles(&self, visitor: &mut dyn HandleVisitor) {
325        let Self { members, woken: _ } = self;
326        for entry in members.values() {
327            entry.behavior.visit_handles(visitor);
328        }
329    }
330}
331
332impl<H: Host> transaction::Transactional for BehaviorSet<H> {
333    type Transaction = BehaviorSetTransaction<H>;
334}
335
336/// Identifier of a behavior that's been inserted into a behavior set, assigned at
337/// insertion time.
338#[derive(Clone, Copy, Eq, Hash, Ord, PartialEq, PartialOrd)]
339struct Key(u64);
340
341impl Key {
342    fn new() -> Self {
343        #![allow(
344            clippy::useless_conversion,
345            clippy::unnecessary_fallible_conversions,
346            reason = "depends on pointer width and atomic support"
347        )]
348
349        use core::sync::atomic::{self, Ordering};
350
351        cfg_if::cfg_if! {
352            // Use 64 bit if possible, because 64 bits is enough to be infeasible to overflow
353            // by counting one at a time.
354            if #[cfg(target_has_atomic = "64")] {
355                static ID_COUNTER: atomic::AtomicU64 = atomic::AtomicU64::new(0);
356            } else if #[cfg(target_has_atomic = "32")] {
357                static ID_COUNTER: atomic::AtomicU32 = atomic::AtomicU32::new(0);
358            } else {
359                // If this doesn't work we'll give up.
360                static ID_COUNTER: atomic::AtomicUsize = atomic::AtomicUsize::new(0);
361            }
362        }
363
364        let id = ID_COUNTER
365            .fetch_update(Ordering::Relaxed, Ordering::Relaxed, |counter| {
366                counter.checked_add(1)
367            })
368            .expect("behavior id overflow");
369
370        Self(id.try_into().unwrap()) // try_into because of usize-to-u64 case
371    }
372}
373
374impl fmt::Display for Key {
375    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
376        fmt::Display::fmt(&self.0, f)
377    }
378}
379impl fmt::Debug for Key {
380    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
381        fmt::Display::fmt(&self.0, f)
382    }
383}
384
385pub(crate) struct BehaviorSetEntry<H: Host> {
386    pub(crate) attachment: H::Attachment,
387    /// Behaviors are stored in [`Arc`] so that they can be used in transactions in ways
388    /// that would otherwise require `Clone + PartialEq`.
389    pub(crate) behavior: Arc<dyn Behavior<H>>,
390    /// None if the entry is not yet inserted in a behavior set.
391    /// TODO: This could be just a separate type or generic instead of a run-time Option.
392    waker: Option<Waker>,
393}
394
395impl<H: Host> Clone for BehaviorSetEntry<H> {
396    fn clone(&self) -> Self {
397        // Manual impl avoids `H: Clone` bound.
398        Self {
399            attachment: self.attachment.clone(),
400            behavior: self.behavior.clone(),
401            waker: self.waker.clone(),
402        }
403    }
404}
405
406impl<H: Host> fmt::Debug for BehaviorSetEntry<H> {
407    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
408        let BehaviorSetEntry {
409            attachment,
410            behavior,
411            waker: _,
412        } = self;
413        behavior.fmt(f)?; // inherit alternate prettyprint mode
414        write!(f, " @ {attachment:?}")?; // don't
415        Ok(())
416    }
417}
418
419impl<H: Host> PartialEq for BehaviorSetEntry<H> {
420    fn eq(&self, other: &Self) -> bool {
421        self.attachment == other.attachment && Arc::ptr_eq(&self.behavior, &other.behavior)
422    }
423}
424
425impl<H: Host> VisitHandles for BehaviorSetEntry<H> {
426    fn visit_handles(&self, visitor: &mut dyn HandleVisitor) {
427        let Self {
428            attachment: _,
429            behavior,
430            waker: _,
431        } = self;
432        behavior.visit_handles(visitor);
433    }
434}
435
436type WokenSet = Mutex<BTreeSet<Key>>;
437
438/// Struct that implements [`Wake`] to provide the [`Waker`] for behavior sets.
439#[derive(Debug)]
440struct BehaviorWakerInner {
441    key: Key,
442    set: Weak<WokenSet>,
443}
444
445impl alloc::task::Wake for BehaviorWakerInner {
446    fn wake(self: Arc<Self>) {
447        self.wake_by_ref()
448    }
449
450    fn wake_by_ref(self: &Arc<Self>) {
451        let Some(strong_set) = self.set.upgrade() else {
452            // behavior set was dropped, so it will never step anything again
453            return;
454        };
455        let Ok(mut mut_set) = strong_set.lock() else {
456            // a previous panic corrupted state
457            return;
458        };
459        mut_set.insert(self.key);
460    }
461}
462
463impl BehaviorWakerInner {
464    fn create_waker(key: Key, woken: &Arc<WokenSet>) -> Waker {
465        Waker::from(Arc::new(BehaviorWakerInner {
466            key,
467            set: Arc::downgrade(woken),
468        }))
469    }
470}
471
472/// Result of [`BehaviorSet::query()`].
473#[non_exhaustive]
474pub struct QueryItem<'a, H: Host, B: Behavior<H> + ?Sized> {
475    /// The found behavior's current value.
476    pub behavior: &'a B,
477    /// The found behavior's current attachment.
478    ///
479    /// An attachment is additional data about “where” the behavior is attached to the host
480    /// what part of the host should be affected by the behavior.
481    pub attachment: &'a H::Attachment,
482}
483
484impl<H: Host, B: Behavior<H> + ?Sized> Clone for QueryItem<'_, H, B> {
485    fn clone(&self) -> Self {
486        // Manual impl avoids `H: Clone` bound.
487        Self {
488            attachment: self.attachment,
489            behavior: self.behavior,
490        }
491    }
492}
493
494impl<H: Host, B: Behavior<H> + ?Sized> fmt::Debug for QueryItem<'_, H, B> {
495    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
496        let QueryItem {
497            attachment,
498            behavior,
499        } = self;
500        behavior.fmt(f)?; // inherit alternate prettyprint mode
501        write!(f, " @ {attachment:?}")?; // don't
502        Ok(())
503    }
504}
505
506/// A [`Transaction`] that adds or modifies [`Behavior`]s in a [`BehaviorSet`].
507#[derive(Debug)]
508#[expect(clippy::module_name_repetitions)] // TODO: rename to Transaction or SetTransaction?
509pub struct BehaviorSetTransaction<H: Host> {
510    /// Replacement of existing behaviors or their attachments, or removal.
511    replace: BTreeMap<Key, Replace<H>>,
512    /// Newly inserted behaviors.
513    insert: Vec<BehaviorSetEntry<H>>,
514}
515
516#[derive(Debug)]
517struct Replace<H: Host> {
518    old: BehaviorSetEntry<H>,
519    /// If None, delete the behavior
520    new: Option<BehaviorSetEntry<H>>,
521}
522
523impl<H: Host> BehaviorSetTransaction<H> {
524    // TODO: replace this with an empty constant or Default::default to compare with, once that's stable in Rust
525    pub(crate) fn is_empty(&self) -> bool {
526        self.replace.is_empty() && self.insert.is_empty()
527    }
528
529    /// This function is private because the normal way it is used is via
530    /// [`Context::replace_self()`]
531    fn replace(key: Key, replacement: Replace<H>) -> Self {
532        BehaviorSetTransaction {
533            replace: BTreeMap::from([(key, replacement)]),
534            ..Default::default()
535        }
536    }
537
538    /// Constructs a transaction that adds a behavior to the behavior set.
539    pub fn insert(attachment: H::Attachment, behavior: Arc<dyn Behavior<H>>) -> Self {
540        BehaviorSetTransaction {
541            insert: vec![BehaviorSetEntry {
542                attachment,
543                behavior,
544                waker: None,
545            }],
546            ..Default::default()
547        }
548    }
549
550    /// This function is private because the normal way it is used is via
551    /// [`Behavior::step()`] returning [`Then::Drop`]
552    fn delete(key: Key, existing_entry: BehaviorSetEntry<H>) -> Self {
553        Self::replace(
554            key,
555            Replace {
556                old: existing_entry,
557                new: None,
558            },
559        )
560    }
561
562    /// Returns an iterator over every behavior attachment added, removed, or modified by
563    /// this transaction (not necessary free of duplicates).
564    pub(crate) fn attachments_affected(&self) -> impl Iterator<Item = &H::Attachment> {
565        let replace = self.replace.values().flat_map(|Replace { old, new }| {
566            [
567                Some(&old.attachment),
568                new.as_ref().map(|entry| &entry.attachment),
569            ]
570            .into_iter()
571            .flatten()
572        });
573        let insert = self.insert.iter().map(|entry| &entry.attachment);
574        replace.chain(insert)
575    }
576}
577
578impl<H: Host> Transaction for BehaviorSetTransaction<H> {
579    type Target = BehaviorSet<H>;
580    type Context<'a> = ();
581    type CommitCheck = CommitCheck;
582    type Output = transaction::NoOutput;
583    type Mismatch = BehaviorTransactionMismatch;
584
585    fn check(
586        &self,
587        target: &BehaviorSet<H>,
588        (): Self::Context<'_>,
589    ) -> Result<Self::CommitCheck, Self::Mismatch> {
590        let Self { replace, insert } = self;
591        // TODO: need to compare replacement preconditions
592        for (&key, Replace { old, new: _ }) in replace {
593            if let Some(BehaviorSetEntry {
594                attachment,
595                behavior,
596                waker: _,
597            }) = target.members.get(&key)
598            {
599                let wrong_attachment = attachment != &old.attachment;
600                let wrong_value = !Arc::ptr_eq(behavior, &old.behavior);
601                if wrong_attachment || wrong_value {
602                    return Err(BehaviorTransactionMismatch {
603                        key,
604                        key_not_found: false,
605                        wrong_attachment,
606                        wrong_value,
607                    });
608                }
609            } else {
610                return Err(BehaviorTransactionMismatch {
611                    key,
612                    key_not_found: true,
613                    wrong_attachment: false,
614                    wrong_value: false,
615                });
616            }
617        }
618
619        // Currently, insertions always succeed.
620        let _ = insert;
621
622        Ok(CommitCheck { _private: () })
623    }
624
625    fn commit(
626        self,
627        target: &mut BehaviorSet<H>,
628        _: Self::CommitCheck,
629        _outputs: &mut dyn FnMut(Self::Output),
630    ) -> Result<(), transaction::CommitError> {
631        for (key, replacement) in self.replace {
632            match replacement.new {
633                Some(new) => {
634                    let Some(entry) = target.members.get_mut(&key) else {
635                        return Err(transaction::CommitError::message::<Self>(format!(
636                            "behavior set does not contain key {key}"
637                        )));
638                    };
639                    let BehaviorSetEntry {
640                        attachment,
641                        behavior,
642                        waker,
643                    } = new;
644                    assert!(
645                        waker.is_none(),
646                        "transaction entries should not have wakers"
647                    );
648                    entry.attachment = attachment;
649                    entry.behavior = behavior;
650                }
651                None => {
652                    let Some(_) = target.members.remove(&key) else {
653                        return Err(transaction::CommitError::message::<Self>(format!(
654                            "behavior set does not contain key {key}"
655                        )));
656                    };
657                }
658            }
659        }
660
661        // TODO: Instead of error, recover by recreating the list
662        let mut woken = target.woken.lock().map_err(|_| {
663            transaction::CommitError::message::<Self>("behavior set wake lock poisoned".into())
664        })?;
665
666        target.members.extend(self.insert.iter().cloned().map(|mut entry| {
667            // Note: This is similar to `BehaviorSet::clone()`.
668
669            let key = Key::new();
670
671            // Mark behavior as to be stepped immediately
672            woken.insert(key);
673
674            // Hook up waker
675            entry.waker = Some(BehaviorWakerInner::create_waker(key, &target.woken));
676
677            (key, entry)
678        }));
679        Ok(())
680    }
681}
682
683impl<H: Host> transaction::Merge for BehaviorSetTransaction<H> {
684    type MergeCheck = MergeCheck;
685    type Conflict = BehaviorTransactionConflict;
686
687    fn check_merge(&self, other: &Self) -> Result<Self::MergeCheck, Self::Conflict> {
688        // Don't allow any touching the same slot at all.
689        if let Some(&key) = self.replace.keys().find(|key| other.replace.contains_key(key)) {
690            return Err(BehaviorTransactionConflict { key });
691        }
692        Ok(MergeCheck { _private: () })
693    }
694
695    fn commit_merge(&mut self, other: Self, _: Self::MergeCheck) {
696        self.replace.extend(other.replace);
697        self.insert.extend(other.insert);
698    }
699}
700
701impl<H: Host> Clone for BehaviorSetTransaction<H> {
702    // Manual implementation to avoid bounds on `H`.
703    fn clone(&self) -> Self {
704        Self {
705            replace: self.replace.clone(),
706            insert: self.insert.clone(),
707        }
708    }
709}
710
711impl<H: Host> Default for BehaviorSetTransaction<H> {
712    // Manual implementation to avoid bounds on `H`.
713    fn default() -> Self {
714        Self {
715            replace: Default::default(),
716            insert: Default::default(),
717        }
718    }
719}
720
721impl<H: Host> PartialEq for BehaviorSetTransaction<H> {
722    // Manual implementation to avoid bounds on `H`.
723    fn eq(&self, other: &Self) -> bool {
724        let Self {
725            replace: r1,
726            insert: i1,
727        } = self;
728        let Self {
729            replace: r2,
730            insert: i2,
731        } = other;
732        r1 == r2 && i1 == i2
733    }
734}
735impl<H: Host> PartialEq for Replace<H> {
736    // Manual implementation to avoid bounds on `H` and to implement the partiality (comparing pointers instead of values).
737    fn eq(&self, other: &Self) -> bool {
738        let Self {
739            old: old1,
740            new: new1,
741        } = self;
742        let Self {
743            old: old2,
744            new: new2,
745        } = other;
746        old1 == old2 && new1 == new2
747    }
748}
749
750impl<H: Host> Eq for BehaviorSetTransaction<H> {}
751impl<H: Host> Eq for Replace<H> {}
752
753impl<H: Host> Clone for Replace<H> {
754    // Manual implementation to avoid bounds on `H`.
755    fn clone(&self) -> Self {
756        Self {
757            old: self.old.clone(),
758            new: self.new.clone(),
759        }
760    }
761}
762
763impl<H: Host> VisitHandles for BehaviorSetTransaction<H> {
764    fn visit_handles(&self, visitor: &mut dyn HandleVisitor) {
765        let Self { replace, insert } = self;
766        for Replace { old, new } in replace.values() {
767            new.visit_handles(visitor);
768            old.visit_handles(visitor);
769        }
770        insert.visit_handles(visitor);
771    }
772}
773
774#[derive(Debug)]
775#[doc(hidden)] // not interesting
776pub struct CommitCheck {
777    _private: (),
778}
779#[derive(Debug)]
780#[doc(hidden)] // not interesting
781pub struct MergeCheck {
782    _private: (),
783}
784
785/// Transaction precondition error type for a [`BehaviorSet`].
786#[derive(Clone, Debug, Eq, PartialEq)]
787#[expect(clippy::module_name_repetitions)]
788pub struct BehaviorTransactionMismatch {
789    key: Key,
790    // These should probably really be an ErrorKind-style enum
791    key_not_found: bool,
792    wrong_attachment: bool,
793    wrong_value: bool,
794}
795
796/// Transaction conflict error type for a [`BehaviorSet`].
797#[derive(Clone, Debug, Eq, PartialEq, displaydoc::Display)]
798#[non_exhaustive]
799#[displaydoc("tried to replace the same behavior slot, {key}, twice")]
800#[expect(clippy::module_name_repetitions)]
801pub struct BehaviorTransactionConflict {
802    key: Key,
803}
804
805impl core::error::Error for BehaviorTransactionMismatch {}
806impl core::error::Error for BehaviorTransactionConflict {}
807
808impl fmt::Display for BehaviorTransactionMismatch {
809    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
810        let &Self {
811            key,
812            key_not_found,
813            wrong_attachment,
814            wrong_value,
815        } = self;
816        write!(f, "behavior {key} ")?;
817        if key_not_found {
818            write!(f, "not found")?;
819        } else {
820            write!(f, "does not have a matching ")?;
821            if wrong_attachment && wrong_value {
822                write!(f, "attachment or value")?;
823            } else if wrong_attachment {
824                write!(f, "attachment")?;
825            } else if wrong_value {
826                write!(f, "value")?;
827            } else {
828                write!(f, "<error in error details>")?;
829            }
830        }
831        Ok(())
832    }
833}
834
835#[cfg(test)]
836pub(crate) use testing::*;
837#[cfg(test)]
838mod testing {
839    use super::*;
840
841    /// A [`Behavior`] implementation that does nothing and carries arbitrary data, for testing.
842    #[derive(Clone, Eq, PartialEq)]
843    pub(crate) struct NoopBehavior<D>(pub D);
844
845    impl<D: fmt::Debug> fmt::Debug for NoopBehavior<D> {
846        fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
847            write!(f, "NoopBehavior(")?;
848            self.0.fmt(f)?; // inherit Formatter::alternate() flag
849            write!(f, ")")?;
850            Ok(())
851        }
852    }
853
854    impl<H: Host, D: fmt::Debug + Send + Sync + 'static> Behavior<H> for NoopBehavior<D> {
855        fn step(&self, _context: &Context<'_, '_, H>) -> (UniverseTransaction, Then) {
856            (UniverseTransaction::default(), Then::Step)
857        }
858        fn persistence(&self) -> Option<Persistence> {
859            None
860        }
861    }
862
863    impl<D> VisitHandles for NoopBehavior<D> {
864        fn visit_handles(&self, _visitor: &mut dyn HandleVisitor) {}
865    }
866}
867
868/// Placeholder for the representation of serializable behaviors.
869///
870/// This type is opaque and cannot be constructed. Future versions of `all-is-cubes` will
871/// offer some means to access this functionality or replace the [`Behavior`] system
872/// entirely.
873#[derive(Debug)]
874pub struct Persistence(
875    #[cfg(feature = "save")] pub(crate) crate::save::schema::BehaviorV1Ser,
876    #[cfg(not(feature = "save"))] (),
877);
878
879/// Performance data returned by [`BehaviorSet::step()`].
880///
881/// Use `Debug` or [`StatusText`] formatting to examine this.
882#[derive(Clone, Copy, Debug, Default, PartialEq)]
883pub(crate) struct BehaviorSetStepInfo {
884    /// Number of behaviors in the set.
885    total_count: usize,
886    /// Time spent performing behavior stepping.
887    total_time: Duration,
888    /// Number of behaviors stepped this tick.
889    stepped: usize,
890    /// Of the stepped behaviors, how many returned a nonempty transaction.
891    acted: usize,
892}
893
894impl ops::AddAssign for BehaviorSetStepInfo {
895    fn add_assign(&mut self, other: Self) {
896        let Self {
897            total_count,
898            total_time,
899            stepped,
900            acted,
901        } = self;
902        *total_count += other.total_count;
903        *total_time += other.total_time;
904        *stepped += other.stepped;
905        *acted += other.acted;
906    }
907}
908
909impl crate::util::Fmt<StatusText> for BehaviorSetStepInfo {
910    fn fmt(&self, f: &mut fmt::Formatter<'_>, fopt: &StatusText) -> fmt::Result {
911        let Self {
912            total_count,
913            total_time,
914            stepped,
915            acted,
916        } = self;
917        write!(
918            f,
919            "{acted} acted of {stepped} stepped of {total_count} in {total_time}",
920            total_time = total_time.refmt(fopt)
921        )
922    }
923}
924
925#[cfg(test)]
926#[allow(
927    unused,
928    reason = "TODO(ecs): BehaviorSet is either going away or getting its tests reworked"
929)]
930mod tests {
931    use super::*;
932    use crate::character::{Character, CharacterTransaction};
933    use crate::math::{FreeCoordinate, GridAab};
934    use crate::physics::BodyTransaction;
935    use crate::space::{Space, SpaceBehaviorAttachment, SpaceTransaction};
936    use crate::time;
937    use crate::transaction::no_outputs;
938    use crate::universe::Universe;
939    use euclid::point3;
940
941    #[cfg(false)] // TODO(ecs): Character no longer implements BehaviorHost
942    #[test]
943    fn behavior_set_debug() {
944        use pretty_assertions::assert_eq;
945
946        let mut set = BehaviorSet::<Character>::new();
947
948        // Empty set
949        assert_eq!(format!("{set:?}"), "BehaviorSet({})");
950        assert_eq!(format!("{set:#?}"), "BehaviorSet({})");
951
952        BehaviorSetTransaction::insert((), Arc::new(NoopBehavior(1)))
953            .execute(&mut set, (), &mut no_outputs)
954            .unwrap();
955        let key = *set.members.keys().next().unwrap();
956
957        // Nonempty set
958        assert_eq!(
959            format!("{set:?}"),
960            format!("BehaviorSet({{{key:?}: NoopBehavior(1) @ ()}})")
961        );
962        assert_eq!(
963            format!("{set:#?}\n"),
964            indoc::formatdoc!(
965                "
966                BehaviorSet({{
967                    {key:?}: NoopBehavior(1) @ (),
968                }})
969            "
970            ),
971        );
972    }
973
974    #[derive(Clone, Debug, PartialEq)]
975    struct SelfModifyingBehavior {
976        foo: u32,
977        then: Then,
978    }
979    #[cfg(false)] // TODO(ecs): Character no longer implements BehaviorHost
980    impl Behavior<Character> for SelfModifyingBehavior {
981        fn step(&self, context: &Context<'_, Character>) -> (UniverseTransaction, Then) {
982            let mut txn = context.bind_host(CharacterTransaction::body(
983                BodyTransaction::default().with_position(point3(
984                    FreeCoordinate::from(self.foo),
985                    0.,
986                    0.,
987                )),
988            ));
989            if self.then != Then::Drop {
990                txn.merge_from(context.replace_self(SelfModifyingBehavior {
991                    foo: self.foo + 1,
992                    ..self.clone()
993                }))
994                .unwrap();
995            }
996            (txn, self.then.clone())
997        }
998        fn persistence(&self) -> Option<Persistence> {
999            None
1000        }
1001    }
1002
1003    impl VisitHandles for SelfModifyingBehavior {
1004        // No handles
1005        fn visit_handles(&self, _visitor: &mut dyn HandleVisitor) {}
1006    }
1007
1008    #[cfg(false)] // TODO(ecs): Character no longer implements BehaviorHost
1009    #[test]
1010    fn self_transaction() {
1011        let mut u = Universe::new();
1012        // TODO: Once we have a simpler type than Character to test with, do that
1013        let space = u.insert_anonymous(Space::empty_positive(1, 1, 1));
1014        let mut character = Character::spawn_default(u.read_ticket(), space);
1015        character.add_behavior(SelfModifyingBehavior {
1016            foo: 1,
1017            then: Then::Step,
1018        });
1019        let character = u.insert("character".into(), character).unwrap();
1020
1021        u.step(false, time::Deadline::Whenever);
1022        u.step(false, time::Deadline::Whenever);
1023
1024        let character = character.read(u.read_ticket()).unwrap();
1025        assert_eq!(
1026            character
1027                .behaviors()
1028                .query::<SelfModifyingBehavior>()
1029                .map(|qi| qi.behavior)
1030                .collect::<Vec<_>>(),
1031            vec![&SelfModifyingBehavior {
1032                foo: 3,
1033                then: Then::Step,
1034            }]
1035        );
1036        assert_eq!(character.body().position().x, 2.0);
1037    }
1038
1039    #[cfg(false)] // TODO(ecs): Character no longer implements BehaviorHost
1040    #[test]
1041    fn dropped_when_requested() {
1042        let mut u = Universe::new();
1043        // TODO: Once we have a simpler type than Character to test with, do that
1044        let space = u.insert_anonymous(Space::empty_positive(1, 1, 1));
1045        let mut character = Character::spawn_default(u.read_ticket(), space);
1046        character.add_behavior(SelfModifyingBehavior {
1047            foo: 1,
1048            then: Then::Drop,
1049        });
1050        let character = u.insert("character".into(), character).unwrap();
1051
1052        assert_eq!(
1053            character
1054                .read(u.read_ticket())
1055                .unwrap()
1056                .behaviors()
1057                .query::<SelfModifyingBehavior>()
1058                .count(),
1059            1
1060        );
1061        u.step(false, time::Deadline::Whenever);
1062        assert_eq!(
1063            character
1064                .read(u.read_ticket())
1065                .unwrap()
1066                .behaviors()
1067                .query::<SelfModifyingBehavior>()
1068                .count(),
1069            0
1070        );
1071    }
1072
1073    #[cfg(false)] // TODO(ecs): Character no longer implements BehaviorHost
1074    #[test]
1075    fn query() {
1076        #[derive(Debug, Eq, PartialEq)]
1077        struct Expected;
1078        #[derive(Debug, Eq, PartialEq)]
1079        struct Unexpected;
1080
1081        let mut set = BehaviorSet::<Character>::new();
1082        let arc_qe = Arc::new(NoopBehavior(Expected));
1083        BehaviorSetTransaction::insert((), arc_qe.clone())
1084            .execute(&mut set, (), &mut no_outputs)
1085            .unwrap();
1086        // different type, so it should not be found
1087        let arc_qu = Arc::new(NoopBehavior(Unexpected));
1088        BehaviorSetTransaction::insert((), arc_qu.clone())
1089            .execute(&mut set, (), &mut no_outputs)
1090            .unwrap();
1091
1092        // Type-specific query should find one
1093        assert_eq!(
1094            set.query::<NoopBehavior<Expected>>().map(|qi| qi.behavior).collect::<Vec<_>>(),
1095            vec![&NoopBehavior(Expected)],
1096        );
1097
1098        // General query should find both
1099        assert_eq!(
1100            set.query_any(None)
1101                .map(|qi| core::ptr::from_ref(qi.behavior))
1102                .collect::<Vec<_>>(),
1103            vec![
1104                Arc::as_ptr(&arc_qe) as *const dyn Behavior<Character>,
1105                Arc::as_ptr(&arc_qu) as *const dyn Behavior<Character>
1106            ],
1107        )
1108    }
1109
1110    #[test]
1111    fn sleep_and_wake() {
1112        use std::sync::mpsc;
1113
1114        #[derive(Debug)]
1115        struct SleepBehavior {
1116            tx: mpsc::Sender<Waker>,
1117        }
1118        impl Behavior<Space> for SleepBehavior {
1119            fn step(&self, context: &Context<'_, '_, Space>) -> (UniverseTransaction, Then) {
1120                self.tx.send(context.waker().clone()).unwrap();
1121                (UniverseTransaction::default(), Then::Sleep)
1122            }
1123            fn persistence(&self) -> Option<Persistence> {
1124                None
1125            }
1126        }
1127        impl VisitHandles for SleepBehavior {
1128            fn visit_handles(&self, _: &mut dyn HandleVisitor) {}
1129        }
1130
1131        // Setup
1132        let (tx, rx) = mpsc::channel();
1133        let mut u = Universe::new();
1134        let space = u.insert("space".into(), Space::empty_positive(1, 1, 1)).unwrap();
1135        SpaceTransaction::add_behavior(GridAab::ORIGIN_CUBE, SleepBehavior { tx })
1136            .bind(space)
1137            .execute(&mut u, (), &mut no_outputs)
1138            .unwrap();
1139        assert_eq!(mpsc::TryRecvError::Empty, rx.try_recv().unwrap_err());
1140
1141        // First step
1142        u.step(false, time::Deadline::Whenever);
1143        let waker: Waker = rx.try_recv().unwrap();
1144
1145        // Second step — should *not* step the behavior because it didn't wake.
1146        u.step(false, time::Deadline::Whenever);
1147        assert_eq!(mpsc::TryRecvError::Empty, rx.try_recv().unwrap_err());
1148
1149        // Wake and step again
1150        waker.wake();
1151        u.step(false, time::Deadline::Whenever);
1152        rx.try_recv().unwrap();
1153    }
1154
1155    #[test]
1156    fn txn_attachments_insert() {
1157        let attachment =
1158            SpaceBehaviorAttachment::new(GridAab::from_lower_size([0, 0, 0], [1, 1, 1]));
1159        let transaction =
1160            BehaviorSetTransaction::<Space>::insert(attachment, Arc::new(NoopBehavior(1)));
1161        assert_eq!(
1162            transaction.attachments_affected().collect::<Vec<_>>(),
1163            vec![&attachment]
1164        );
1165    }
1166
1167    #[test]
1168    fn txn_attachments_replace() {
1169        let attachment1 =
1170            SpaceBehaviorAttachment::new(GridAab::from_lower_size([0, 0, 0], [1, 1, 1]));
1171        let attachment2 =
1172            SpaceBehaviorAttachment::new(GridAab::from_lower_size([10, 0, 0], [1, 1, 1]));
1173        let transaction = BehaviorSetTransaction::<Space>::replace(
1174            Key::new(),
1175            Replace {
1176                old: BehaviorSetEntry {
1177                    attachment: attachment1,
1178                    behavior: Arc::new(NoopBehavior(1)),
1179                    waker: None,
1180                },
1181                new: Some(BehaviorSetEntry {
1182                    attachment: attachment2,
1183                    behavior: Arc::new(NoopBehavior(1)),
1184                    waker: None,
1185                }),
1186            },
1187        );
1188        assert_eq!(
1189            transaction.attachments_affected().collect::<Vec<_>>(),
1190            vec![&attachment1, &attachment2]
1191        );
1192    }
1193
1194    #[test]
1195    fn txn_check_non_matching_old() {
1196        // Set up behavior set
1197        let mut set = BehaviorSet::<Space>::new();
1198        let attachment =
1199            SpaceBehaviorAttachment::new(GridAab::from_lower_size([0, 0, 0], [1, 1, 1]));
1200        let correct_old_behavior = Arc::new(NoopBehavior(1));
1201        BehaviorSetTransaction::insert(attachment, correct_old_behavior.clone())
1202            .execute(&mut set, (), &mut no_outputs)
1203            .unwrap();
1204        let key = *set.members.keys().next().unwrap();
1205
1206        // Try mismatched behavior
1207        {
1208            let transaction = BehaviorSetTransaction::<Space>::replace(
1209                key,
1210                Replace {
1211                    old: BehaviorSetEntry {
1212                        attachment,
1213                        behavior: Arc::new(NoopBehavior(2)), // not equal to actual behavior
1214                        waker: None,
1215                    },
1216                    new: Some(BehaviorSetEntry {
1217                        attachment,
1218                        behavior: Arc::new(NoopBehavior(3)),
1219                        waker: None,
1220                    }),
1221                },
1222            );
1223            assert_eq!(
1224                transaction.check(&set, ()).unwrap_err(),
1225                BehaviorTransactionMismatch {
1226                    key,
1227                    key_not_found: false,
1228                    wrong_attachment: false,
1229                    wrong_value: true,
1230                }
1231            );
1232        }
1233
1234        // Try mismatched attachment
1235        {
1236            let transaction = BehaviorSetTransaction::<Space>::replace(
1237                key,
1238                Replace {
1239                    old: BehaviorSetEntry {
1240                        // not equal to attachment
1241                        attachment: SpaceBehaviorAttachment::new(GridAab::from_lower_size(
1242                            [100, 0, 0],
1243                            [1, 1, 1],
1244                        )),
1245                        behavior: correct_old_behavior,
1246                        waker: None,
1247                    },
1248                    new: Some(BehaviorSetEntry {
1249                        attachment,
1250                        behavior: Arc::new(NoopBehavior(4)),
1251                        waker: None,
1252                    }),
1253                },
1254            );
1255            assert_eq!(
1256                transaction.check(&set, ()).unwrap_err(),
1257                BehaviorTransactionMismatch {
1258                    key,
1259                    key_not_found: false,
1260                    wrong_attachment: true,
1261                    wrong_value: false,
1262                }
1263            );
1264        }
1265    }
1266
1267    #[cfg(false)] // TODO(ecs): Character no longer implements BehaviorHost
1268    #[test]
1269    fn txn_systematic() {
1270        let b1 = Arc::new(SelfModifyingBehavior {
1271            foo: 100,
1272            then: Then::Step,
1273        });
1274        let b2 = Arc::new(SelfModifyingBehavior {
1275            foo: 200,
1276            then: Then::Step,
1277        });
1278
1279        // TODO: test replace() but we'll need to be able to discover the keys
1280
1281        transaction::TransactionTester::new()
1282            .transaction(BehaviorSetTransaction::default(), |_, _| Ok(()))
1283            .transaction(BehaviorSetTransaction::insert((), b1), |_, after| {
1284                after
1285                    .query::<SelfModifyingBehavior>()
1286                    .map(|item| item.behavior)
1287                    .find(|b| b.foo == 100)
1288                    .ok_or("expected b1")?;
1289                Ok(())
1290            })
1291            .transaction(BehaviorSetTransaction::insert((), b2), |_, after| {
1292                after
1293                    .query::<SelfModifyingBehavior>()
1294                    .map(|item| item.behavior)
1295                    .find(|b| b.foo == 200)
1296                    .ok_or("expected b2")?;
1297                Ok(())
1298            })
1299            .target(BehaviorSet::new)
1300            .test(())
1301    }
1302}