all_is_cubes/
universe.rs

1//! [`Universe`], the top-level game-world container.
2
3use alloc::boxed::Box;
4use alloc::collections::BTreeMap;
5use alloc::sync::Arc;
6use alloc::vec::Vec;
7use core::any::Any;
8use core::fmt;
9
10use bevy_ecs::entity::Entity;
11use bevy_ecs::prelude as ecs;
12use bevy_ecs::world::FromWorld as _;
13use manyfmt::Fmt;
14
15use crate::block::{self, BlockDefStepInfo};
16use crate::character::{self, Character};
17use crate::physics::{self, BodyStepInfo};
18use crate::save::WhenceUniverse;
19use crate::space::{self, Space, SpaceStepInfo};
20use crate::time;
21use crate::transaction::{self, ExecuteError, Transaction, Transactional};
22use crate::util::{ConciseDebug, Refmt as _, ShowStatus, StatusText};
23
24#[cfg(feature = "rerun")]
25use crate::rerun_glue as rg;
26
27// -------------------------------------------------------------------------------------------------
28
29// Note: Most things in `members` are either an impl, private, or intentionally public-in-private.
30// Therefore, no glob reexport.
31mod members;
32pub(crate) use members::*;
33#[allow(
34    clippy::module_name_repetitions,
35    reason = "TODO: rename UniverseMember to Member or something"
36)]
37pub use members::{AnyHandle, UniverseMember};
38
39mod ecs_details;
40use ecs_details::NameMap;
41// TODO(ecs): try to eliminate uses of get_one_mut_and_ticket in favor of normal queries
42pub use ecs_details::PubliclyMutableComponent;
43pub(crate) use ecs_details::{CurrentStep, InfoCollector, Membership, QueryStateBundle};
44
45mod gc;
46
47mod universe_txn;
48pub use universe_txn::*;
49
50mod handle;
51pub use handle::*;
52
53mod handle_set;
54#[doc(hidden)]
55#[expect(clippy::module_name_repetitions, reason = "false positive")]
56pub use handle_set::{HandleSet, PartialUniverse};
57
58pub(crate) mod tl;
59
60mod id;
61#[expect(clippy::module_name_repetitions)] // TODO: consider renaming to Id
62pub use id::UniverseId;
63
64mod name;
65pub use name::Name;
66
67mod ticket;
68pub(crate) use ticket::QueryBlockDataSources;
69pub use ticket::{ReadTicket, ReadTicketError};
70
71mod visit;
72pub use visit::*;
73
74#[cfg(test)]
75mod tests;
76
77// -------------------------------------------------------------------------------------------------
78
79/// A collection of named objects which can refer to each other via [`Handle`],
80/// and which are simulated at the same time steps.
81///
82/// A [`Universe`] consists of:
83///
84/// * _members_ of [various types], which may be identified using [`Name`]s or [`Handle`]s.
85/// * A [`time::Clock`] defining how time is considered to pass in it.
86/// * A [`WhenceUniverse`] defining where its data is persisted, if anywhere.
87/// * A [`UniverseId`] unique within this process.
88///
89/// [`Universe`] is a quite large data structure, so it may be desirable to keep it in a
90/// [`Box`], especially when being passed through `async` blocks.
91///
92/// [various types]: UniverseMember
93///
94#[doc = include_str!("save/serde-warning.md")]
95pub struct Universe {
96    /// ECS storage for most of the data of the universe.
97    world: ecs::World,
98
99    /// [`QueryState`]s derived from `world` that we use manually and therefore need to keep fresh.
100    queries: Queries,
101
102    id: UniverseId,
103
104    /// Next number to assign to a [`Name::Anonym`].
105    next_anonym: usize,
106
107    /// Whether to run a garbage collection on the next [`Self::step()`].
108    /// This is set to true whenever a new member is inserted, which policy ensures
109    /// that repeated insertion and dropping references cannot lead to unbounded growth
110    /// as long as steps occur routinely.
111    wants_gc: bool,
112
113    /// Where the contents of `self` came from, and where they might be able to be written
114    /// back to.
115    ///
116    /// For universes created by [`Universe::new()`], this is equal to `Arc::new(())`.
117    pub whence: Arc<dyn WhenceUniverse>,
118
119    /// Number of [`step()`]s which have occurred since this [`Universe`] was created.
120    ///
121    /// Note that this value is not serialized; thus, it is reset whenever “a saved game
122    /// is loaded”. It should only be used for diagnostics or other non-persistent state.
123    ///
124    /// When a step is in progress, this is updated before stepping any members.
125    ///
126    /// TODO: This is not *currently* used for anything and should be removed if it never
127    /// is. Its current reason to exist is for some not-yet-merged diagnostic logging.
128    /// It may also serve a purpose when I refine the notion of simulation tick rate.
129    ///
130    /// [`step()`]: Universe::step
131    session_step_time: u64,
132
133    spaces_with_work: usize,
134
135    #[cfg(feature = "rerun")]
136    rerun_destination: crate::rerun_glue::Destination,
137}
138
139#[derive(ecs::FromWorld)]
140#[macro_rules_attribute::derive(ecs_details::derive_manual_query_bundle!)]
141struct Queries {
142    all_members_query: ecs::QueryState<(Entity, &'static Membership)>,
143    read_members: MemberReadQueryStates,
144    write_members: MemberWriteQueryStates,
145}
146
147impl Universe {
148    /// Constructs an empty [`Universe`].
149    ///
150    /// [`Universe`] is a very large struct, and it is often manipulated by `async fn`s; therefore,
151    /// to avoid surprisingly large stack frame or future sizes, and needless memory copies, it is
152    /// returned in a [`Box`]. Feel free to unbox it if appropriate.
153    pub fn new() -> Box<Self> {
154        let empty_box = Box::new_uninit();
155        let id = UniverseId::new();
156
157        let mut world = bevy_ecs::world::World::new();
158
159        // Configure the World state.
160        {
161            // Configure schedules.
162            {
163                let mut schedules = world.get_resource_or_init::<ecs::Schedules>();
164                // Insist on no ambiguous scheduling.
165                schedules.configure_schedules(bevy_ecs::schedule::ScheduleBuildSettings {
166                    ambiguity_detection: bevy_ecs::schedule::LogLevel::Error,
167                    ..Default::default()
168                });
169            }
170
171            // Register various components and resources which are *not* visible state of the
172            // universe, but have data derived from others or are used temporarily.
173            world.init_resource::<NameMap>();
174            world.init_resource::<CurrentStep>();
175            world.register_component::<Membership>();
176            Self::register_all_member_components(&mut world);
177            InfoCollector::<BlockDefStepInfo>::register(&mut world);
178            InfoCollector::<BodyStepInfo>::register(&mut world);
179            InfoCollector::<space::LightUpdatesInfo>::register(&mut world);
180
181            // Register things that are user-visible state of the universe.
182            // When new such resources are added, also mention them in the documentation when
183            // they aren’t just implementation details.
184            // TODO: allow configuring nondefault clock schedules
185            world.insert_resource(time::Clock::new(time::TickSchedule::per_second(60), 0));
186            world.insert_resource(id);
187
188            // Add systems.
189            gc::add_gc(&mut world);
190            block::add_block_def_systems(&mut world);
191            character::add_main_systems(&mut world);
192            character::add_eye_systems(&mut world);
193            physics::step::add_systems(&mut world);
194            space::step::add_space_systems(&mut world);
195        }
196
197        Box::write(
198            empty_box,
199            Universe {
200                queries: Queries::from_world(&mut world),
201
202                world,
203                id,
204                next_anonym: 0,
205                wants_gc: false,
206                whence: Arc::new(()),
207                session_step_time: 0,
208                spaces_with_work: 0,
209                #[cfg(feature = "rerun")]
210                rerun_destination: Default::default(),
211            },
212        )
213    }
214
215    /// Returns a [`Handle`] for the object in this universe with the given name,
216    /// regardless of its type, or [`None`] if there is none.
217    ///
218    /// This is a dynamically-typed version of [`Universe::get()`].
219    pub fn get_any(&self, name: &Name) -> Option<&AnyHandle> {
220        self.world.resource::<NameMap>().map.get(name)
221    }
222
223    /// Returns the character named `"character"`.
224    /// This is currently assumed to be the “player character” for this universe.
225    ///
226    /// TODO: this is a temporary shortcut to be replaced with something with more nuance.
227    pub fn get_default_character(&self) -> Option<Handle<Character>> {
228        self.get(&"character".into())
229    }
230
231    /// Returns a unique identifier for this particular [`Universe`] (within this memory space).
232    ///
233    /// It may be used to determine whether a given [`Handle`] belongs to this universe or not.
234    pub fn universe_id(&self) -> UniverseId {
235        self.id
236    }
237
238    /// Returns a [`ReadTicket`] that may be used for reading the members of this universe.
239    #[track_caller]
240    pub fn read_ticket(&self) -> ReadTicket<'_> {
241        ReadTicket::from_universe(self)
242    }
243
244    /// Execute the given transaction on the given handle's referent.
245    ///
246    /// This equivalent to, but more efficient than, creating a single-member
247    /// [`UniverseTransaction`],
248    ///
249    /// Returns an error if the transaction's preconditions are not met,
250    /// if the transaction encountered an internal error, if the referent
251    /// was already being read or written (which is expressed as an
252    /// [`ExecuteError::Commit`], because it is a shouldn’t-happen kind of error),
253    /// or if the handle does not belong to this universe.
254    #[inline(never)]
255    #[expect(private_bounds, reason = "TransactionOnEcs is internal")]
256    pub fn execute_1<T>(
257        &mut self,
258        handle: &Handle<T>,
259        transaction: <T as Transactional>::Transaction,
260    ) -> Result<(), ExecuteError<<T as Transactional>::Transaction>>
261    where
262        T::Transaction: TransactionOnEcs,
263        T: UniverseMember + Transactional,
264    {
265        let check = check_transaction_in_universe(self, handle, &transaction)
266            .map_err(ExecuteError::Check)?;
267        commit_transaction_in_universe(self, handle, transaction, check)
268            .map_err(ExecuteError::Commit)
269    }
270
271    /// Advance time for all members.
272    ///
273    /// * `deadline` is when to stop computing flexible things such as light transport.
274    pub fn step(&mut self, paused: bool, deadline: time::Deadline) -> UniverseStepInfo {
275        let start_time = time::Instant::now();
276
277        self.world.run_schedule(time::schedule::BeforeStepReset);
278
279        let tick = self.world.resource_mut::<time::Clock>().advance(paused);
280        let step_input = ecs_details::StepInput {
281            tick,
282            deadline,
283            budget_per_space: deadline
284                .remaining_since(start_time)
285                .map(|dur| dur / u32::try_from(self.spaces_with_work).unwrap_or(1).max(1)),
286        };
287        self.world.resource_mut::<CurrentStep>().0 = Some(step_input.clone());
288
289        self.log_rerun_time();
290
291        if self.wants_gc {
292            self.gc();
293            self.wants_gc = false;
294        }
295
296        if !tick.paused() {
297            self.session_step_time += 1;
298        }
299
300        // --- End of setup; now advance time for our contents. ---
301
302        // We run Synchronize between each overall stage of stepping.
303        // TODO: Rethink exactly how much of this is needed.
304        self.world.run_schedule(time::schedule::Synchronize);
305
306        self.world.run_schedule(time::schedule::BeforeStep);
307
308        // Synchronize in case BeforeStep did anything.
309        self.world.run_schedule(time::schedule::Synchronize);
310
311        // Execute the tick actions of blocks in spaces.
312        if !paused {
313            // TODO: put this system in a schedule once there are no obstacles to doing so.
314            self.world
315                .run_system_cached(space::step::execute_tick_actions_system)
316                .unwrap()
317                .unwrap();
318        }
319
320        let transactions_from_space_behaviors = self
321            .world
322            .run_system_cached(space::step::step_behaviors_system)
323            .unwrap()
324            .unwrap();
325
326        self.world.run_schedule(time::schedule::Synchronize);
327
328        self.world.run_system_cached(space::step::update_light_system).unwrap().unwrap();
329
330        if !tick.paused() {
331            self.world.run_schedule(time::schedule::Step);
332        }
333
334        // Finalize behavior stuff
335
336        // TODO: Quick hack -- we would actually like to execute non-conflicting transactions and skip conflicting ones...
337        for t in transactions_from_space_behaviors {
338            if let Err(e) = t.execute(self, (), &mut transaction::no_outputs) {
339                // TODO: Need to report these failures back to the source
340                // ... and perhaps in the UniverseStepInfo
341                log::info!("Transaction failure: {e}");
342            }
343        }
344
345        self.world.run_schedule(time::schedule::Synchronize);
346
347        self.world.run_schedule(time::schedule::AfterStep);
348
349        self.world.run_schedule(time::schedule::Synchronize);
350
351        // Post-step state cleanup
352        self.world.resource_mut::<CurrentStep>().0 = None;
353        //self.world.run_schedule(time::schedule::AfterStepReset); // TODO(ecs): move things into this
354
355        // Gather info
356        let computation_time = time::Instant::now().saturating_duration_since(start_time);
357        let space_step =
358            self.world.run_system_cached(space::step::collect_space_step_info).unwrap();
359        self.spaces_with_work = space_step.light.active_spaces;
360        UniverseStepInfo {
361            computation_time,
362            active_members: self.spaces_with_work, // TODO: incomplete
363            total_members: space_step.light.total_spaces, // TODO: incomplete
364            block_def_step: self
365                .world
366                .resource_mut::<InfoCollector<BlockDefStepInfo>>()
367                .finish_collection(),
368            body_step: self.world.resource_mut::<InfoCollector<BodyStepInfo>>().finish_collection(),
369            space_step,
370        }
371    }
372
373    /// Returns the [`time::Clock`] that is used to advance time when [`step()`](Self::step)
374    /// is called.
375    pub fn clock(&self) -> time::Clock {
376        *self.world.resource()
377    }
378
379    /// Replaces the universe's [`time::Clock`], used by [`step()`](Self::step).
380    ///
381    /// Use this to control how many steps there are per second, or reset time to a specific value.
382    pub fn set_clock(&mut self, clock: time::Clock) {
383        self.world.insert_resource(clock);
384    }
385
386    /// Inserts a new object without giving it a specific name, and returns
387    /// a handle to it.
388    ///
389    /// Anonymous members are subject to garbage collection on the next [`Universe::step()`];
390    /// the returned handle should be used or converted to a [`StrongHandle`] before then.
391    //---
392    // TODO: This should logically return `StrongHandle`, but that may be too disruptive.
393    // For now, we live with "do something with this handle before the next step".
394    pub fn insert_anonymous<T>(&mut self, value: T) -> Handle<T>
395    where
396        T: UniverseMember,
397    {
398        self.insert(Name::Pending, value)
399            .expect("shouldn't happen: insert_anonymous failed")
400    }
401
402    /// Translates a name for an object of type `T` into a [`Handle`] for it.
403    ///
404    /// Returns [`None`] if no object exists for the name or if its type is not `T`.
405    pub fn get<T>(&self, name: &Name) -> Option<Handle<T>>
406    where
407        T: UniverseMember,
408    {
409        self.world
410            .resource::<NameMap>()
411            .map
412            .get(name)
413            .and_then(AnyHandle::downcast_ref)
414            .cloned()
415    }
416
417    /// Inserts a new object with a specific name.
418    ///
419    /// Returns an error if the name is already in use.
420    pub fn insert<T>(&mut self, name: Name, value: T) -> Result<Handle<T>, InsertError>
421    where
422        T: UniverseMember,
423    {
424        let handle = Handle::new_pending(name);
425        MemberBoilerplate::into_any_pending(handle.clone(), Some(Box::new(value)))
426            .insert_pending_into_universe(self)?;
427        Ok(handle)
428    }
429
430    /// Returns a `Handle` to a member whose referent may or may not be deserialized yet.
431    #[cfg(feature = "save")]
432    pub(crate) fn get_or_insert_deserializing<T>(
433        &mut self,
434        name: Name,
435    ) -> Result<Handle<T>, InsertError>
436    where
437        T: UniverseMember,
438    {
439        match name {
440            Name::Pending => {
441                return Err(InsertError {
442                    name,
443                    kind: InsertErrorKind::InvalidName,
444                });
445            }
446            Name::Specific(_) | Name::Anonym(_) => {}
447        }
448        if let Some(handle) = self.get(&name) {
449            Ok(handle)
450        } else {
451            Ok(Handle::new_deserializing(name, self))
452        }
453    }
454
455    /// As `insert()`, but for assigning values to names that _might_ have gotten
456    /// [`Self::get_or_insert_deserializing()`] called on them.
457    #[cfg(feature = "save")]
458    pub(crate) fn insert_deserialized<T>(
459        &mut self,
460        name: Name,
461        value: Box<T>,
462    ) -> Result<(), InsertError>
463    where
464        T: UniverseMember,
465    {
466        self.get_or_insert_deserializing(name.clone())?
467            .insert_deserialized_value(self, value)
468            .map_err(|()| InsertError {
469                name,
470                kind: InsertErrorKind::DeserializeMultipleValues,
471            })
472    }
473
474    /// Iterate over all of the objects of type `T`.
475    /// Note that this includes anonymous objects.
476    ///
477    /// ```
478    /// use all_is_cubes::block::{Block, BlockDef};
479    /// use all_is_cubes::content::make_some_blocks;
480    /// use all_is_cubes::universe::{Name, Universe, Handle};
481    ///
482    /// let mut universe = Universe::new();
483    /// let [block_1, block_2] = make_some_blocks();
484    /// universe.insert(Name::from("b1"), BlockDef::new(universe.read_ticket(), block_1.clone()));
485    /// universe.insert(Name::from("b2"), BlockDef::new(universe.read_ticket(), block_2.clone()));
486    ///
487    /// let mut found_blocks = universe.iter_by_type()
488    ///     .map(|(name, value): (Name, Handle<BlockDef>)| {
489    ///         (name, value.read(universe.read_ticket()).unwrap().block().clone())
490    ///     })
491    ///     .collect::<Vec<_>>();
492    /// found_blocks.sort_by_key(|(name, _)| name.to_string());
493    /// assert_eq!(
494    ///     found_blocks,
495    ///     vec![Name::from("b1"), Name::from("b2")].into_iter()
496    ///         .zip(vec![block_1, block_2])
497    ///         .collect::<Vec<_>>(),
498    /// );
499    /// ```
500    pub fn iter_by_type<T>(&self) -> impl Iterator<Item = (Name, Handle<T>)>
501    where
502        T: UniverseMember,
503    {
504        self.queries.all_members_query.iter_manual(&self.world).filter_map(
505            |(_entity, membership)| {
506                Some((
507                    membership.name.clone(),
508                    membership.handle.downcast_ref::<T>()?.clone(),
509                ))
510            },
511        )
512    }
513
514    /// Convert a possibly-[pending](Name::Pending) [`Name`] into a name that may be an
515    /// actual name in this universe (which is always either [`Name::Specific`] or
516    /// [`Name::Anonym`] if it succeeds).
517    ///
518    /// Fails if:
519    ///
520    /// * The name is already present.
521    /// * The name is an [`Name::Anonym`] (which may not be pre-selected, only allocated).
522    fn allocate_name(&mut self, proposed_name: &Name) -> Result<Name, InsertError> {
523        // TODO: This logic is semi-duplicated in MemberTxn::check.
524        // Resolve that by making all inserts happen via transactions, or by sharing
525        // the code (this will need a "don't actually allocate anonym" mode).
526
527        match proposed_name {
528            Name::Specific(_) => {
529                // Check that the name is not already used, under *any* type.
530                if self.get_any(proposed_name).is_some() {
531                    return Err(InsertError {
532                        name: proposed_name.clone(),
533                        kind: InsertErrorKind::AlreadyExists,
534                    });
535                }
536                Ok(proposed_name.clone())
537            }
538            Name::Anonym(_) => Err(InsertError {
539                name: proposed_name.clone(),
540                kind: InsertErrorKind::InvalidName,
541            }),
542            Name::Pending => {
543                let new_name = Name::Anonym(self.next_anonym);
544                self.next_anonym += 1;
545
546                assert!(
547                    self.get_any(&new_name).is_none(),
548                    "shouldn't happen: newly created anonym already in use"
549                );
550                Ok(new_name)
551            }
552        }
553    }
554
555    /// Delete a member.
556    ///
557    /// (Use [`UniverseTransaction::delete()`] as the public, checked interface to this.)
558    ///
559    /// Returns whether the entry actually existed.
560    pub(crate) fn delete(&mut self, name: &Name) -> bool {
561        let Some(handle) = self.get_any(name) else {
562            return false;
563        };
564        let entity = handle.as_entity(self.id).unwrap();
565        handle.set_state_to_gone(GoneReason::Deleted {});
566        let success = self.world.despawn(entity);
567        assert!(success);
568        true
569    }
570
571    /// Perform garbage collection: delete all anonymous members which have no handles to them.
572    ///
573    /// This may happen at any time during operations of the universe; calling this method
574    /// merely ensures that it happens now and not earlier.
575    pub fn gc(&mut self) {
576        self.world.run_schedule(gc::Gc);
577    }
578
579    /// Validate handles in a universe after deserializing it.
580    ///
581    /// If this finds a handle which was used but not defined, deserialization will fail.
582    #[cfg(feature = "save")]
583    pub(crate) fn validate_deserialized_members(&self) -> Result<(), DeserializeHandlesError> {
584        let read_ticket = self.read_ticket();
585        self.queries.all_members_query.iter_manual(&self.world).try_for_each(
586            |(_entity, membership)| {
587                if membership.handle.not_still_deserializing(read_ticket) {
588                    Ok(())
589                } else {
590                    Err(DeserializeHandlesError {
591                        to: membership.handle.name(),
592                    })
593                }
594            },
595        )
596    }
597
598    /// An escape hatch from the [`Transaction`] and [`Handle`] system: allows mutating components
599    /// directly, if they permit this type of access.
600    #[doc(hidden)]
601    #[inline(never)]
602    pub fn mutate_component<C, T, Out>(
603        &mut self,
604        handle: &Handle<T>,
605        function: impl FnOnce(&mut C) -> Out,
606    ) -> Result<Out, HandleError>
607    where
608        C: PubliclyMutableComponent<T> + ecs::Component<Mutability = bevy_ecs::component::Mutable>,
609        T: UniverseMember,
610    {
611        let entity = handle.as_entity(self.id)?;
612        if let Some(component_mut) = self.world.get_mut::<C>(entity).map(ecs::Mut::into_inner) {
613            Ok(function(component_mut))
614        } else {
615            // This should never happen even with concurrent access, because as_entity() checks
616            // all cases that would lead to the entity being absent.
617            panic!(
618                "{handle:?}.as_entity() succeeded but entity {entity:?} or component is missing"
619            );
620        }
621    }
622
623    /// Obtain a [`space::Mutation`] to modify a [`Space`].
624    ///
625    /// This is useful for efficient bulk mutations and to call operations that cannot be expressed
626    /// as transactions, such as [`space::Mutation::fast_evaluate_light()`].
627    ///
628    /// Returns an error if the given [`Handle`] does not belong to this universe.
629    #[inline(never)]
630    pub fn mutate_space<'u, Out>(
631        &'u mut self,
632        handle: &Handle<Space>,
633        function: impl FnOnce(&mut space::Mutation<'_, 'u>) -> Out,
634    ) -> Result<Out, HandleError> {
635        let entity = handle.as_entity(self.id)?;
636        let Ok(write_query_data) =
637            self.queries.write_members.spaces.get_mut(&mut self.world, entity)
638        else {
639            // This should never happen even with concurrent access, because as_entity() checks
640            // all cases that would lead to the entity being absent.
641            panic!("{handle:?}.as_entity() succeeded but query failed");
642        };
643        // TODO(ecs): non-stub ticket using `get_one_mut_and_ticket()`
644        Ok(space::Mutation::with_write_query(
645            ReadTicket::stub(),
646            write_query_data,
647            function,
648        ))
649    }
650
651    /// Update stored queries to account for new archetypes.
652    ///
653    /// This is called whenever [`UniverseMember`]s are added.
654    fn update_archetypes(&mut self) {
655        self.queries.update_archetypes(&self.world);
656    }
657
658    /// Activate logging this universe's time to a Rerun stream.
659    #[cfg(feature = "rerun")]
660    pub fn log_to_rerun(&mut self, destination: rg::Destination) {
661        self.rerun_destination = destination;
662
663        // Initialize axes.
664        // TODO: this should be per-Space in principle
665        self.rerun_destination.log_static(
666            &rg::entity_path![],
667            &rg::archetypes::ViewCoordinates::new(
668                rg::components::ViewCoordinates::from_up_and_handedness(
669                    crate::math::Face6::PY.into(),
670                    rg::view_coordinates::Handedness::Right,
671                ),
672            ),
673        );
674
675        // Write current timepoint
676        self.log_rerun_time();
677    }
678
679    /// Activate logging some member's actions to a Rerun stream.
680    ///
681    /// What exact information this means depends on the specific member type.
682    #[cfg(feature = "rerun")]
683    pub fn log_member_to_rerun<T: UniverseMember>(
684        &mut self,
685        handle: &Handle<T>,
686        destination: crate::rerun_glue::Destination,
687    ) -> Result<(), HandleError> {
688        self.world.entity_mut(handle.as_entity(self.id)?).insert(destination);
689        self.update_archetypes();
690        Ok(())
691    }
692
693    #[allow(clippy::unused_self)]
694    #[mutants::skip]
695    fn log_rerun_time(&self) {
696        #[cfg(feature = "rerun")]
697        #[expect(clippy::cast_possible_wrap)]
698        self.rerun_destination
699            .stream
700            .set_time_sequence("session_step_time", self.session_step_time as i64);
701    }
702}
703
704impl fmt::Debug for Universe {
705    fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
706        let Self {
707            world,
708            queries: _,
709            id: _,
710            next_anonym: _,
711            wants_gc: _,
712            whence,
713            session_step_time,
714            spaces_with_work,
715            #[cfg(feature = "rerun")]
716                rerun_destination: _,
717        } = self;
718
719        let mut ds = fmt.debug_struct("Universe");
720
721        if {
722            let whence: &dyn WhenceUniverse = &**whence;
723            <dyn Any>::downcast_ref::<()>(whence)
724        }
725        .is_none()
726        {
727            ds.field("whence", &whence);
728        }
729        ds.field("clock", &self.clock());
730        ds.field("session_step_time", &session_step_time);
731        ds.field("spaces_with_work", &spaces_with_work);
732
733        if false {
734            // A more “raw” dump of the ECS world which doesn't depend for correctness on
735            // all_members_query having been updated, and can see non-"member" entities.
736            // TODO(ecs): Decide whether to keep or discard this.
737            let raw_entities: Vec<(Option<Name>, Vec<&str>)> = world
738                .iter_entities()
739                .map(|er| {
740                    let components = er
741                        .archetype()
742                        .components()
743                        .map(|cid| world.components().get_info(cid).unwrap().name())
744                        .collect();
745                    let name = er.get::<Membership>().map(|m| m.name.clone());
746                    (name, components)
747                })
748                .collect();
749            ds.field("raw_entities", &raw_entities);
750        }
751
752        // Print members, sorted by name.
753        let members: BTreeMap<&Name, &'static str> = self
754            .queries
755            .all_members_query
756            .iter_manual(&self.world)
757            .map(|(_entity, membership)| (&membership.name, membership.handle.member_type_name()))
758            .collect();
759        for (member_name, type_name) in members {
760            ds.field(
761                &format!("{member_name}"),
762                &type_name.refmt(&manyfmt::formats::Unquote),
763            );
764        }
765
766        ds.finish_non_exhaustive()
767    }
768}
769
770impl Default for Universe {
771    fn default() -> Self {
772        *Self::new()
773    }
774}
775
776/// Errors resulting from attempting to insert an object in a [`Universe`].
777#[derive(Clone, Debug, Eq, Hash, PartialEq)]
778#[non_exhaustive]
779pub struct InsertError {
780    /// The name given for the insertion.
781    pub name: Name,
782    /// The problem that was detected.
783    pub kind: InsertErrorKind,
784}
785
786/// Specific problems with attempting to insert an object in a [`Universe`].
787/// A component of [`InsertError`].
788//---
789// TODO(ecs): Many of these errors should be impossible now that there is always exactly one,
790// non-clonable, UniverseTransaction that can insert any given handle, but the code that generates
791// these errors is not yet in a position to acknowledge that.
792#[derive(Clone, Debug, Eq, Hash, PartialEq)]
793#[non_exhaustive]
794pub enum InsertErrorKind {
795    /// An object already exists with the proposed name.
796    AlreadyExists,
797
798    /// The proposed name may not be used.
799    ///
800    /// In particular, a [`Name::Anonym`] may not be inserted explicitly.
801    InvalidName,
802
803    /// The provided [`Handle`] was created by [`Handle::new_gone()`] and is intentionally useless.
804    Gone,
805
806    /// A value has not been associated with the handle.
807    /// This can occur when using [`UniverseTransaction::insert_without_value()`].
808    ValueMissing,
809
810    /// The provided [`Handle`]’s value is being mutated and cannot
811    /// be checked.
812    InUse,
813
814    /// The provided [`Handle`] was already inserted into some universe.
815    AlreadyInserted,
816
817    /// A serialized [`Universe`] contained multiple definitions for this name.
818    DeserializeMultipleValues,
819
820    /// The provided value contains handles to a different universe.
821    CrossUniverse,
822
823    #[doc(hidden)] // should be unreachable
824    /// The provided [`Handle`] is being used in the deserialization process
825    /// and cannot be inserted otherwise.
826    Deserializing,
827
828    #[doc(hidden)]
829    /// The provided [`Handle`] experienced an error during a previous operation and
830    /// cannot be used.
831    Poisoned,
832}
833
834impl core::error::Error for InsertError {}
835
836impl fmt::Display for InsertError {
837    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
838        let Self { name, kind } = self;
839        match kind {
840            InsertErrorKind::AlreadyExists => {
841                write!(f, "an object already exists with name {name}")
842            }
843            InsertErrorKind::InvalidName => {
844                write!(f, "the name {name} may not be used in an insert operation")
845            }
846            InsertErrorKind::Gone => write!(
847                f,
848                "cannot insert handle {name} created by Handle::new_gone()"
849            ),
850            InsertErrorKind::ValueMissing => write!(f, "value has not been provided for {name}"),
851            InsertErrorKind::InUse => write!(
852                f,
853                "the object {name} is being mutated during this insertion attempt"
854            ),
855            InsertErrorKind::AlreadyInserted => write!(f, "the object {name} is already inserted"),
856            InsertErrorKind::DeserializeMultipleValues => {
857                write!(f, "duplicate definition of object {name}")
858            }
859            InsertErrorKind::CrossUniverse => {
860                write!(f, "the object {name} contains handles to another universe")
861            }
862            InsertErrorKind::Deserializing => write!(
863                f,
864                "the object {name} is already in a universe being deserialized"
865            ),
866            InsertErrorKind::Poisoned => {
867                write!(f, "the object is invalid due to a previous failure")
868            }
869        }
870    }
871}
872
873#[cfg(feature = "save")]
874#[derive(Clone, Debug, Eq, PartialEq, displaydoc::Display)]
875#[displaydoc("data contains a handle to {to} that was not defined")]
876pub(crate) struct DeserializeHandlesError {
877    /// Name in the bad handle.
878    to: Name,
879}
880
881/// Performance data returned by [`Universe::step`].
882///
883/// The exact contents of this structure
884/// are unstable; use only `Debug` formatting to examine its contents unless you have
885/// a specific need for one of the values.
886#[derive(Clone, Debug, Default, PartialEq)]
887#[non_exhaustive]
888#[expect(clippy::module_name_repetitions)] // TODO: consider renaming to StepInfo
889pub struct UniverseStepInfo {
890    #[doc(hidden)]
891    pub computation_time: time::Duration,
892    /// Number of members which needed to do something specific.
893    pub(crate) active_members: usize,
894    /// Number of members which were processed at all.
895    pub(crate) total_members: usize,
896    pub(crate) block_def_step: BlockDefStepInfo,
897    pub(crate) body_step: BodyStepInfo,
898    pub(crate) space_step: SpaceStepInfo,
899}
900impl core::ops::AddAssign<UniverseStepInfo> for UniverseStepInfo {
901    fn add_assign(&mut self, other: Self) {
902        self.computation_time += other.computation_time;
903        self.active_members += other.active_members;
904        self.total_members += other.total_members;
905        self.block_def_step += other.block_def_step;
906        self.body_step += other.body_step;
907        self.space_step += other.space_step;
908    }
909}
910impl Fmt<StatusText> for UniverseStepInfo {
911    fn fmt(&self, fmt: &mut fmt::Formatter<'_>, fopt: &StatusText) -> fmt::Result {
912        let Self {
913            computation_time,
914            active_members,
915            total_members,
916            block_def_step,
917            body_step,
918            space_step,
919        } = self;
920        writeln!(
921            fmt,
922            "Step computation: {t} for {active_members} of {total_members}",
923            t = computation_time.refmt(&ConciseDebug),
924        )?;
925        if fopt.show.contains(ShowStatus::BLOCK) {
926            writeln!(fmt, "Block defs: {}", block_def_step.refmt(fopt))?;
927        }
928        if fopt.show.contains(ShowStatus::CHARACTER) {
929            writeln!(fmt, "{}", body_step.refmt(fopt))?;
930        }
931        if fopt.show.contains(ShowStatus::SPACE) {
932            writeln!(fmt, "{}", space_step.refmt(fopt))?;
933        }
934        Ok(())
935    }
936}