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