use std::any::{self, TypeId};
use std::sync::Arc;
use crate::entity::{deletion, ealloc, generation, rctrack, Ealloc, Raw};
use crate::scheduler::Scheduler;
use crate::tracer::Tracer;
use crate::{comp, entity, system, Archetype, Entity, Global, Storage};
mod builder;
pub use builder::Builder;
pub(crate) mod global;
pub use global::{SyncGlobals, UnsyncGlobals};
pub(crate) mod accessor;
pub use accessor::Components;
pub(crate) mod typed;
pub mod offline;
pub trait Bundle {
fn register(&mut self, _builder: &mut Builder) {}
fn populate(&mut self, _world: &mut World) {}
}
pub fn new(bundles: impl IntoIterator<Item = Box<dyn Bundle>>) -> World {
new_with_concurrency(
bundles,
match std::thread::available_parallelism() {
Ok(c) => c.get(),
Err(err) => {
log::error!("Cannot detect number of CPUs ({err}), parallelism disabled");
0
}
},
)
}
pub fn new_unthreaded(bundles: impl IntoIterator<Item = Box<dyn Bundle>>) -> World {
new_with_concurrency(bundles, 0)
}
pub fn new_with_concurrency(
bundles: impl IntoIterator<Item = Box<dyn Bundle>>,
concurrency: usize,
) -> World {
let mut bundles: Vec<_> = bundles.into_iter().collect();
let mut builder = Builder::new(concurrency);
for bundle in &mut bundles {
bundle.register(&mut builder);
}
let mut world = builder.build();
for bundle in &mut bundles {
bundle.populate(&mut world);
}
world
}
pub struct World {
ealloc_map: ealloc::Map,
pub components: Components,
scheduler: Scheduler,
pub sync_globals: SyncGlobals,
pub unsync_globals: UnsyncGlobals,
pub rctrack: rctrack::MaybeStoreMap,
}
impl World {
pub fn execute(&mut self, tracer: &impl Tracer) {
self.scheduler.execute(
tracer,
&mut self.components,
&mut self.sync_globals,
&mut self.unsync_globals,
&mut self.rctrack,
&mut self.ealloc_map,
);
}
pub fn create<A: Archetype>(&mut self, comp_map: comp::Map<A>) -> Entity<A> {
self.create_with_hint::<A>(Default::default(), comp_map)
}
pub fn create_with_hint<A: Archetype>(
&mut self,
hint: <A::Ealloc as Ealloc>::AllocHint,
comp_map: comp::Map<A>,
) -> Entity<A> {
let ealloc = match self.ealloc_map.map.get_mut(&TypeId::of::<A>()) {
Some(ealloc) => ealloc,
None => panic!(
"Cannot create entity for archetype {} because it is not used in any systems",
any::type_name::<A>()
),
};
let ealloc = ealloc.as_any_mut().downcast_mut::<A::Ealloc>().expect("TypeId mismatch");
let id = ealloc.allocate(hint);
let allocated = Entity::new_allocated(id);
init_entity(
&mut self.sync_globals,
id,
allocated.rc.clone(),
&mut self.rctrack,
&mut self.components,
comp_map,
);
allocated
}
pub(crate) fn as_mut(&mut self) -> (WorldMut<'_>, Vec<(&str, &mut dyn system::Descriptor)>) {
let system_refs = self.scheduler.get_system_refs();
(
WorldMut {
ealloc_map: &mut self.ealloc_map,
components: &mut self.components,
sync_globals: &mut self.sync_globals,
unsync_globals: &mut self.unsync_globals,
rctrack: &mut self.rctrack,
},
system_refs,
)
}
pub fn delete<E: entity::Ref>(&mut self, entity: E) -> DeleteResult {
let id = entity.id();
drop(entity);
let (world, mut systems) = self.as_mut();
let result = flag_delete_entity::<E::Archetype>(id, world, &mut systems[..]);
if let DeleteResult::Terminating = result {
self.scheduler.offline_buffer().rerun_queue.push(Box::new(offline::DeleteEntity::<
E::Archetype,
> {
entity: id,
})
as Box<dyn offline::Operation>);
}
result
}
pub fn get_global<G: Global + Send + Sync>(&mut self) -> &mut G {
let global = match self.sync_globals.sync_globals.get_mut(&TypeId::of::<G>()) {
Some((_, global)) => global,
None => panic!(
"The global state {} cannot be retrieved because it is not used in any systems, \
or was registered as an unsync global instead of a sync global",
any::type_name::<G>()
),
};
global.get_mut().downcast_mut::<G>().expect("TypeId mismatch")
}
pub fn get_global_unsync<G: Global>(&mut self) -> &mut G {
let global = match self.unsync_globals.unsync_globals.get_mut(&TypeId::of::<G>()) {
Some((_, global)) => global,
None => panic!(
"The global state {} cannot be retrieved because it is not used in any systems, \
or was registered as a sync global instead of an unsync global",
any::type_name::<G>()
),
};
global.downcast_mut::<G>().expect("TypeId mismatch")
}
}
pub(crate) struct WorldMut<'t> {
pub(crate) ealloc_map: &'t mut ealloc::Map,
pub(crate) components: &'t mut Components,
pub(crate) sync_globals: &'t mut SyncGlobals,
pub(crate) unsync_globals: &'t mut UnsyncGlobals,
pub(crate) rctrack: &'t mut rctrack::MaybeStoreMap,
}
impl<'t> WorldMut<'t> {
pub(crate) fn as_mut(&mut self) -> WorldMut<'_> {
WorldMut {
ealloc_map: &mut *self.ealloc_map,
components: &mut *self.components,
sync_globals: &mut *self.sync_globals,
unsync_globals: &mut *self.unsync_globals,
rctrack: &mut *self.rctrack,
}
}
}
fn init_entity<A: Archetype>(
sync_globals: &mut SyncGlobals,
id: A::RawEntity,
_rc: entity::MaybeArc,
_rctrack: &mut rctrack::MaybeStoreMap,
components: &mut Components,
comp_map: comp::Map<A>,
) {
sync_globals.get_mut::<generation::StoreMap>().next::<A>(id.to_primitive());
#[cfg(any(
all(debug_assertions, feature = "debug-entity-rc"),
all(not(debug_assertions), feature = "release-entity-rc"),
))]
{
_rctrack.0.set::<A>(id.to_primitive(), _rc);
}
let typed = components.archetype_mut::<A>();
typed.init_entity(id, comp_map);
}
#[derive(Debug, Clone, Copy)]
pub enum DeleteResult {
Deleted,
Terminating,
}
fn flag_delete_entity<'t, A: Archetype>(
id: A::RawEntity,
world: WorldMut<'t>,
systems: &mut [(&str, &mut dyn system::Descriptor)],
) -> DeleteResult {
let storage = world
.components
.archetype_mut::<A>()
.simple_storages
.get_mut(&TypeId::of::<deletion::Flag>())
.expect("deletion::Flags storage is always available");
storage.get_storage::<deletion::Flag>().set(id, Some(deletion::Flag(())));
try_real_delete_entity::<A>(id, world, systems)
}
#[allow(unused_variables)]
fn try_real_delete_entity<'t, A: Archetype>(
entity: <A as Archetype>::RawEntity,
world: WorldMut<'t>,
systems: &mut [(&str, &mut dyn system::Descriptor)],
) -> DeleteResult {
let storages = &mut world.components.archetype_mut::<A>().simple_storages;
let has_finalizer = storages.values_mut().any(|storage| {
Arc::get_mut(&mut storage.storage)
.expect("storage arc was leaked")
.get_mut()
.has_finalizer(entity)
});
if has_finalizer {
return DeleteResult::Terminating;
}
for storage in storages.values_mut() {
Arc::get_mut(&mut storage.storage)
.expect("storage arc was leaked")
.get_mut()
.clear_entry(entity);
}
#[cfg(any(
all(debug_assertions, feature = "debug-entity-rc"),
all(not(debug_assertions), feature = "release-entity-rc"),
))]
{
use crate::util::DbgTypeId;
let rc = world.rctrack.0.remove::<A>(entity.to_primitive());
if Arc::try_unwrap(rc).is_err() {
let found = search_references(
world.components,
world.sync_globals,
world.unsync_globals,
systems,
DbgTypeId::of::<A>(),
entity.to_primitive(),
);
panic!(
"Detected dangling strong reference to entity {}#{entity:?} in {}. All strong \
references to an entity must be dropped before queuing for deletion and removing \
all finalizers.",
any::type_name::<A>(),
found.join(", ")
);
}
}
let ealloc = world
.ealloc_map
.map
.get_mut(&TypeId::of::<A>())
.expect("Attempted to delete entity of unknown archetype");
let ealloc: &mut A::Ealloc = ealloc.as_any_mut().downcast_mut().expect("TypeId mismatch");
ealloc.queue_deallocate(entity);
DeleteResult::Deleted
}
#[cfg(any(
all(debug_assertions, feature = "debug-entity-rc"),
all(not(debug_assertions), feature = "release-entity-rc"),
))]
fn search_references(
components: &mut Components,
sync_globals: &mut SyncGlobals,
unsync_globals: &mut UnsyncGlobals,
systems: &mut [(&str, &mut dyn system::Descriptor)],
archetype: crate::util::DbgTypeId,
entity: usize,
) -> Vec<String> {
use std::any::Any;
use crate::entity::referrer::search_single::SearchSingleStrong;
use crate::entity::referrer::VisitMutArg;
let mut state = SearchSingleStrong::new(archetype, entity);
for (name, system) in systems {
let mut object = system.visit_mut();
state._set_debug_name(format!("system {name}"));
object.0.search_single_strong(&mut state);
}
let globals = sync_globals
.sync_globals
.iter_mut()
.map(|(global_ty, (vtable, value))| {
(global_ty, vtable, &mut **value.get_mut() as &mut dyn Any)
})
.chain(
unsync_globals
.unsync_globals
.iter_mut()
.map(|(global_ty, (vtable, value))| (global_ty, vtable, &mut **value)),
);
for (global_ty, vtable, value) in globals {
state._set_debug_name(format!("global state {global_ty}"));
vtable.search_single_strong(value, &mut state);
}
for (iter_archetype, typed) in &mut components.archetypes {
typed.referrer_dyn_iter(&iter_archetype.to_string()).search_single_strong(&mut state);
}
state.found
}
#[cfg(test)]
pub(crate) mod tests;