1use 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
27mod 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;
41pub 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)] pub 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#[doc = include_str!("save/serde-warning.md")]
95pub struct Universe {
96 world: ecs::World,
98
99 queries: Queries,
101
102 id: UniverseId,
103
104 next_anonym: usize,
106
107 wants_gc: bool,
112
113 pub whence: Arc<dyn WhenceUniverse>,
118
119 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 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 {
161 {
163 let mut schedules = world.get_resource_or_init::<ecs::Schedules>();
164 schedules.configure_schedules(bevy_ecs::schedule::ScheduleBuildSettings {
166 ambiguity_detection: bevy_ecs::schedule::LogLevel::Error,
167 ..Default::default()
168 });
169 }
170
171 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 world.insert_resource(time::Clock::new(time::TickSchedule::per_second(60), 0));
186 world.insert_resource(id);
187
188 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 pub fn get_any(&self, name: &Name) -> Option<&AnyHandle> {
220 self.world.resource::<NameMap>().map.get(name)
221 }
222
223 pub fn get_default_character(&self) -> Option<Handle<Character>> {
228 self.get(&"character".into())
229 }
230
231 pub fn universe_id(&self) -> UniverseId {
235 self.id
236 }
237
238 #[track_caller]
240 pub fn read_ticket(&self) -> ReadTicket<'_> {
241 ReadTicket::from_universe(self)
242 }
243
244 #[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 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 self.world.run_schedule(time::schedule::Synchronize);
305
306 self.world.run_schedule(time::schedule::BeforeStep);
307
308 self.world.run_schedule(time::schedule::Synchronize);
310
311 if !paused {
313 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 for t in transactions_from_space_behaviors {
338 if let Err(e) = t.execute(self, (), &mut transaction::no_outputs) {
339 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 self.world.resource_mut::<CurrentStep>().0 = None;
353 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, total_members: space_step.light.total_spaces, 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 pub fn clock(&self) -> time::Clock {
376 *self.world.resource()
377 }
378
379 pub fn set_clock(&mut self, clock: time::Clock) {
383 self.world.insert_resource(clock);
384 }
385
386 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 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 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 #[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 #[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 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 fn allocate_name(&mut self, proposed_name: &Name) -> Result<Name, InsertError> {
523 match proposed_name {
528 Name::Specific(_) => {
529 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 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 pub fn gc(&mut self) {
576 self.world.run_schedule(gc::Gc);
577 }
578
579 #[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 #[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 panic!(
618 "{handle:?}.as_entity() succeeded but entity {entity:?} or component is missing"
619 );
620 }
621 }
622
623 #[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 panic!("{handle:?}.as_entity() succeeded but query failed");
642 };
643 Ok(space::Mutation::with_write_query(
645 ReadTicket::stub(),
646 write_query_data,
647 function,
648 ))
649 }
650
651 fn update_archetypes(&mut self) {
655 self.queries.update_archetypes(&self.world);
656 }
657
658 #[cfg(feature = "rerun")]
660 pub fn log_to_rerun(&mut self, destination: rg::Destination) {
661 self.rerun_destination = destination;
662
663 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 self.log_rerun_time();
677 }
678
679 #[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 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 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#[derive(Clone, Debug, Eq, Hash, PartialEq)]
778#[non_exhaustive]
779pub struct InsertError {
780 pub name: Name,
782 pub kind: InsertErrorKind,
784}
785
786#[derive(Clone, Debug, Eq, Hash, PartialEq)]
793#[non_exhaustive]
794pub enum InsertErrorKind {
795 AlreadyExists,
797
798 InvalidName,
802
803 Gone,
805
806 ValueMissing,
809
810 InUse,
813
814 AlreadyInserted,
816
817 DeserializeMultipleValues,
819
820 CrossUniverse,
822
823 #[doc(hidden)] Deserializing,
827
828 #[doc(hidden)]
829 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 to: Name,
879}
880
881#[derive(Clone, Debug, Default, PartialEq)]
887#[non_exhaustive]
888#[expect(clippy::module_name_repetitions)] pub struct UniverseStepInfo {
890 #[doc(hidden)]
891 pub computation_time: time::Duration,
892 pub(crate) active_members: usize,
894 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}