use std::any::{Any, TypeId};
use std::collections::HashMap;
use parking_lot::RwLock;
use super::typed;
use crate::entity::{ealloc, generation, referrer};
use crate::system::spec;
use crate::util::DbgTypeId;
use crate::{scheduler, system, Global};
pub struct Builder {
scheduler: scheduler::Builder,
archetypes: HashMap<DbgTypeId, (ealloc::AnyBuilder, Box<dyn typed::AnyBuilder>)>,
sync_globals: GlobalBuilderMap<dyn Any + Send + Sync>,
unsync_globals: GlobalBuilderMap<dyn Any>,
}
enum GlobalBuilder<G: ?Sized> {
Provided(Box<G>),
Missing(fn() -> Box<G>),
}
impl Builder {
pub fn new(concurrency: usize) -> Self {
Self {
scheduler: scheduler::Builder::new(concurrency),
archetypes: HashMap::new(),
sync_globals: {
let mut map = HashMap::new();
populate_default_globals(&mut map);
map
},
unsync_globals: HashMap::new(),
}
}
fn archetype(
&mut self,
archetype: spec::ArchetypeDescriptor,
) -> &mut Box<dyn typed::AnyBuilder> {
&mut self.archetypes.entry(archetype.id).or_insert_with(archetype.builder).1
}
fn register_resources(
&mut self,
system: system::Spec,
type_visitor: referrer::VisitTypeArg,
state_maybe_uninit: &[TypeId],
sync: bool,
node: scheduler::Node,
) {
for arch in type_visitor.found_archs {
if state_maybe_uninit.contains(&arch.id) {
continue;
}
self.scheduler.add_dependencies(
vec![spec::Dependency::Before(Box::new(system::EntityCreationPartition {
ty: arch,
}))],
node,
);
}
for request in system.global_requests {
match (request.initial, sync) {
(spec::GlobalInitial::Sync(initial), _) => {
if self.unsync_globals.contains_key(&request.ty) {
panic!(
"Global type {} is used as both thread-safe and thread-local",
request.ty
);
}
self.sync_globals
.entry(request.ty)
.or_insert_with(|| (request.vtable, GlobalBuilder::Missing(initial)));
self.scheduler.use_resource(
node,
scheduler::ResourceType::Global(request.ty),
scheduler::ResourceAccess::new(request.mutable),
);
}
(spec::GlobalInitial::Unsync(_), true) => {
panic!(
"Cannot schedule system {} as thread-safe because it requires \
thread-unsafe resources",
system.debug_name,
);
}
(spec::GlobalInitial::Unsync(initial), false) => {
if self.sync_globals.contains_key(&request.ty) {
panic!(
"Global type {} is used as both thread-safe and thread-local",
request.ty
);
}
self.unsync_globals
.entry(request.ty)
.or_insert_with(|| (request.vtable, GlobalBuilder::Missing(initial)));
self.scheduler.use_resource(
node,
scheduler::ResourceType::Global(request.ty),
scheduler::ResourceAccess::new(request.mutable),
);
}
}
for &strong_ref in &request.strong_refs {
self.scheduler.add_dependencies(
vec![spec::Dependency::Before(Box::new(system::EntityCreationPartition {
ty: strong_ref,
}))],
node,
);
}
}
for request in system.simple_requests {
let builder = self.archetype(request.arch);
builder.add_simple_storage_if_missing(request.comp, request.storage_builder);
self.scheduler.use_resource(
node,
scheduler::ResourceType::Simple { arch: request.arch.id, comp: request.comp },
scheduler::ResourceAccess::new(request.mutable),
);
for &strong_ref in &request.strong_refs {
self.scheduler.add_dependencies(
vec![spec::Dependency::Before(Box::new(system::EntityCreationPartition {
ty: strong_ref,
}))],
node,
);
}
}
for request in system.isotope_requests {
let builder = self.archetype(request.arch);
builder.add_isotope_map_if_missing(request.comp, request.map_builder);
self.scheduler.use_resource(
node,
scheduler::ResourceType::Isotope { arch: request.arch.id, comp: request.comp },
scheduler::ResourceAccess::with_discrim(request.mutable, request.discrim.clone()),
);
for &strong_ref in &request.strong_refs {
self.scheduler.add_dependencies(
vec![spec::Dependency::Before(Box::new(system::EntityCreationPartition {
ty: strong_ref,
}))],
node,
);
}
}
for request in system.entity_creator_requests {
if !request.no_partition {
self.scheduler.add_dependencies(
vec![spec::Dependency::After(Box::new(system::EntityCreationPartition {
ty: request.arch,
}))],
node,
);
}
}
self.scheduler.add_dependencies(system.dependencies, node);
}
pub fn schedule(&mut self, system: impl system::Sendable) {
self.schedule_boxed(Box::new(system))
}
pub fn schedule_boxed(&mut self, system: Box<dyn system::Sendable>) {
let mut type_visitor = referrer::VisitTypeArg::new();
system.visit_type(&mut type_visitor);
let state_maybe_uninit = system.state_maybe_uninit();
let (node, spec) = self.scheduler.push_send_system(system);
self.register_resources(spec, type_visitor, &state_maybe_uninit, true, node);
}
pub fn schedule_thread_unsafe(&mut self, system: impl system::Unsendable) {
self.schedule_thread_unsafe_boxed(Box::new(system))
}
pub fn schedule_thread_unsafe_boxed(&mut self, system: Box<dyn system::Unsendable>) {
let mut type_visitor = referrer::VisitTypeArg::new();
system.visit_type(&mut type_visitor);
let state_maybe_uninit = system.state_maybe_uninit();
let (node, spec) = self.scheduler.push_unsend_system(system);
self.register_resources(spec, type_visitor, &state_maybe_uninit, false, node);
}
pub fn global<G: Global + Send + Sync>(&mut self, value: G) {
self.sync_globals.insert(
DbgTypeId::of::<G>(),
(referrer::SingleVtable::of::<G>(), GlobalBuilder::Provided(Box::new(value))),
);
}
pub fn global_thread_unsafe<G: Global>(&mut self, value: G) {
self.unsync_globals.insert(
DbgTypeId::of::<G>(),
(referrer::SingleVtable::of::<G>(), GlobalBuilder::Provided(Box::new(value))),
);
}
pub fn set_concurrency(&mut self, concurrency: usize) {
self.scheduler.concurrency = concurrency;
}
pub fn build(self) -> super::World {
let (ealloc_map, storages) = self
.archetypes
.into_iter()
.map(|(ty, (ealloc, storages))| {
((ty, ealloc(self.scheduler.concurrency + 1)), (ty, storages.build()))
})
.unzip();
let ealloc_map = ealloc::Map::new(ealloc_map);
let storages = super::Components { archetypes: storages };
let sync_globals = self
.sync_globals
.into_iter()
.map(|(ty, (vtable, global_builder))| {
(
ty,
(
vtable,
RwLock::new(match global_builder {
GlobalBuilder::Provided(value) => value,
GlobalBuilder::Missing(default) => default(),
}),
),
)
})
.collect();
let sync_globals = super::SyncGlobals { sync_globals };
let unsync_globals = self
.unsync_globals
.into_iter()
.map(|(ty, (vtable, global_builder))| {
(
ty,
(
vtable,
match global_builder {
GlobalBuilder::Provided(value) => value,
GlobalBuilder::Missing(default) => default(),
},
),
)
})
.collect();
let unsync_globals = super::UnsyncGlobals { unsync_globals };
super::World {
ealloc_map,
components: storages,
sync_globals,
unsync_globals,
scheduler: self.scheduler.build(),
rctrack: Default::default(),
}
}
}
fn populate_default_globals(map: &mut GlobalBuilderMap<dyn Any + Send + Sync>) {
fn put_global<T: Any + Send + Sync + referrer::Referrer>(
map: &mut GlobalBuilderMap<dyn Any + Send + Sync>,
value: T,
) {
map.insert(
DbgTypeId::of::<T>(),
(referrer::SingleVtable::of::<T>(), GlobalBuilder::Provided(Box::new(value))),
);
}
put_global(map, generation::StoreMap::default());
}
type GlobalBuilderMap<T> = HashMap<DbgTypeId, (referrer::SingleVtable, GlobalBuilder<T>)>;