#![doc = include_str!("../README.md")]
use std::{
any::TypeId,
collections::{HashMap, HashSet},
};
use crate::{
command_buffer::Command,
components::{AnyComponent, Component, Components},
systems::{
ComponentSystem, ComponentSystemStorage, EntitySystem, EntitySystemStorage, TickingSystem,
TickingSystemStorage, UniverseSystem, UniverseSystemStorage,
},
};
pub mod components;
pub mod query;
pub mod systems;
mod command_buffer;
pub use command_buffer::CommandBuffer;
mod entity;
pub use entity::{Entity, EntityBuilder};
mod world;
use tracing::warn;
pub use world::{World, WorldBuilder, WorldId};
pub struct Universe {
worlds: HashMap<WorldId, World>,
next_world_id: WorldId,
entities: HashMap<Entity, WorldId>,
next_entity_id: Entity,
#[allow(clippy::struct_field_names)]
universe_systems: UniverseSystemStorage,
ticking_systems: TickingSystemStorage,
entity_systems: EntitySystemStorage,
component_systems: ComponentSystemStorage,
components: Components,
buffers: Vec<CommandBuffer>,
}
impl Universe {
#[must_use]
pub fn builder() -> UniverseBuilder {
UniverseBuilder::new()
}
#[must_use]
fn build(builder: UniverseBuilder) -> Self {
let mut universe = Self {
worlds: HashMap::new(),
next_world_id: WorldId::default(),
entities: HashMap::new(),
next_entity_id: Entity::default(),
universe_systems: builder.universe_systems,
ticking_systems: builder.ticking_systems,
entity_systems: builder.entity_systems,
component_systems: builder.component_systems,
components: Components::default(),
buffers: Vec::new(),
};
let mut cmd = CommandBuffer::new();
for builder in builder.worlds {
cmd.create_world(builder);
}
universe.submit_buffer(cmd);
universe
}
pub fn tick(&mut self, delta_time: f64) {
let mut cmd = CommandBuffer::new();
let buffers = std::mem::take(&mut self.buffers);
let mut commands: Vec<Command> = Vec::new();
for sub in buffers {
commands.append(&mut sub.commands());
}
self.run_commands(&mut cmd, commands);
self.universe_systems
.iter()
.for_each(|system| system.tick(&mut cmd, self, delta_time));
self.ticking_systems.iter().for_each(|system| {
system.tick(&mut cmd, self, delta_time, &mut system.query().query(self));
});
self.submit_buffer(cmd);
}
#[allow(clippy::too_many_lines)]
fn run_commands(&mut self, cmd: &mut CommandBuffer, commands: Vec<Command>) {
let mut created_worlds: HashSet<WorldId> = HashSet::new();
let mut destroyed_worlds: HashSet<WorldId> = HashSet::new();
let mut spawned_entities: HashSet<Entity> = HashSet::new();
let mut despawned_entities: HashSet<Entity> = HashSet::new();
let mut sent_entities: HashSet<(Entity, WorldId, WorldId)> = HashSet::new(); let mut added_components: HashSet<(Entity, TypeId)> = HashSet::new();
let mut updated_components: Vec<(Entity, TypeId, Box<dyn AnyComponent>)> = Vec::new(); let mut removed_components: HashSet<(Entity, TypeId)> = HashSet::new();
for command in commands {
match command {
Command::CreateWorld(builder) => {
let id = self.next_world_id;
self.next_world_id += 1;
let world = World::new(id, builder.name);
self.worlds.insert(id, world);
for builder in builder.entities {
let entity = self.add_entity(id).expect("just created world");
spawned_entities.insert(entity);
builder.components.into_values().for_each(|component| {
let type_id = component.component_type_id();
self.components.insert_any(entity, component);
added_components.insert((entity, type_id));
});
}
created_worlds.insert(id);
}
Command::DestroyWorld(world) => {
let Some(world) = self.worlds.get(&world) else {
continue;
};
for &entity in &world.alive {
despawned_entities.insert(entity);
for (type_id, _) in self.components.get_all(entity) {
removed_components.insert((entity, type_id));
}
}
destroyed_worlds.insert(world.id());
}
Command::Spawn(world, builder) => {
let Some(entity) = self.add_entity(world) else {
warn!("cannot add entity to world {world} as it does not exist");
continue;
};
spawned_entities.insert(entity);
builder.components.into_values().for_each(|component| {
let type_id = component.component_type_id();
self.components.insert_any(entity, component);
added_components.insert((entity, type_id));
});
}
Command::Despawn(entity) => {
if !self.is_alive(entity) {
continue;
}
despawned_entities.insert(entity);
for (type_id, _) in self.components.get_all(entity) {
removed_components.insert((entity, type_id));
}
}
Command::Send(entity, to) => {
let Some(from) = self.entities.get(&entity).copied() else {
warn!("cannot send {entity:?} as it does not exist");
continue;
};
if from == to {
continue;
}
if !self.worlds.contains_key(&to) {
warn!("cannot send {entity:?} to world {to} as it does not exist");
continue;
}
let old = self
.worlds
.get_mut(&from)
.expect("entity is registered as in this world");
old.alive.remove(&entity);
let new = self.worlds.get_mut(&to).expect("just checked it exists");
new.alive.insert(entity);
self.entities.insert(entity, to);
sent_entities.insert((entity, from, to));
}
Command::SetComponent(entity, component) => {
if !self.is_alive(entity) {
continue;
}
let type_id = component.component_type_id();
if let Some(old) = self.components.insert_any(entity, component) {
updated_components.push((entity, type_id, old));
} else {
added_components.insert((entity, type_id));
}
}
Command::RemoveComponent(entity, type_id) => {
if self.components.get_any(entity, type_id).is_some() {
removed_components.insert((entity, type_id));
}
}
}
}
for world in created_worlds {
let world = self.worlds.get(&world).expect("we just added this world");
self.universe_systems
.iter()
.for_each(|system| system.world_created(cmd, self, world));
}
for entity in spawned_entities {
self.universe_systems
.iter()
.for_each(|system| system.entity_spawned(cmd, self, entity));
self.entity_systems.iter().for_each(|system| {
if !system.query().matches(self, entity) {
return;
}
system.spawned(cmd, self, entity);
});
}
for (entity, type_id) in added_components {
let component = self
.components
.get_any(entity, type_id)
.expect("just added component");
self.component_systems
.iter(type_id)
.for_each(|system| system.added(cmd, entity, component));
}
for (entity, type_id, old) in updated_components {
let component = self
.components
.get_any(entity, type_id)
.expect("just updated component");
self.component_systems
.iter(type_id)
.for_each(|system| system.updated(cmd, entity, old.as_ref(), component));
}
for (entity, from, to) in sent_entities {
self.universe_systems
.iter()
.for_each(|system| system.entity_sent(cmd, self, entity, from, to));
self.entity_systems.iter().for_each(|system| {
if !system.query().matches(self, entity) {
return;
}
system.sent(cmd, self, entity, from, to);
});
}
for &(entity, type_id) in &removed_components {
let component = self
.components
.get_any(entity, type_id)
.expect("haven't removed component yet");
self.component_systems
.iter(type_id)
.for_each(|system| system.removed(cmd, entity, component));
}
for &entity in &despawned_entities {
self.universe_systems
.iter()
.for_each(|system| system.entity_despawned(cmd, self, entity));
self.entity_systems.iter().for_each(|system| {
if !system.query().matches(self, entity) {
return;
}
system.despawned(cmd, self, entity);
});
}
for world in &destroyed_worlds {
let world = self.worlds.get(world).expect("world not added yet");
self.universe_systems
.iter()
.for_each(|system| system.world_destroyed(cmd, self, world));
}
for (entity, type_id) in removed_components {
self.components.remove_any(entity, type_id);
}
for entity in despawned_entities {
if let Some(world) = self.get_world(entity)
&& let Some(world) = self.worlds.get_mut(&world)
{
world.alive.remove(&entity);
}
self.entities.remove(&entity);
}
for world in destroyed_worlds {
self.worlds.remove(&world);
}
}
fn add_entity(&mut self, world: WorldId) -> Option<Entity> {
let world = self.worlds.get_mut(&world)?;
let entity = self.next_entity_id;
self.next_entity_id += 1;
self.entities.insert(entity, world.id());
world.alive.insert(entity);
Some(entity)
}
pub fn submit_buffer(&mut self, buffer: CommandBuffer) {
if buffer.is_empty() {
return;
}
self.buffers.push(buffer);
}
#[must_use]
pub fn world(&self, world: WorldId) -> Option<&World> {
self.worlds.get(&world)
}
#[must_use]
pub fn get_world(&self, entity: Entity) -> Option<WorldId> {
self.entities.get(&entity).copied()
}
#[must_use]
pub fn is_in_world(&self, world: WorldId, entity: Entity) -> bool {
self.entities.get(&entity) == Some(&world)
}
#[must_use]
pub fn alive_count(&self) -> usize {
self.entities.len()
}
#[must_use]
pub fn is_alive(&self, entity: Entity) -> bool {
self.entities.contains_key(&entity)
}
#[must_use]
pub fn component<C: Component>(&self, entity: Entity) -> Option<&C> {
self.components.get(entity)
}
pub fn create_world(&mut self, builder: WorldBuilder) -> Option<WorldId> {
let next_id = self.next_world_id;
let mut cmd = CommandBuffer::new();
self.run_commands(&mut cmd, vec![Command::CreateWorld(builder)]);
self.submit_buffer(cmd);
let new_next = self.next_world_id;
if next_id == new_next {
None
} else {
Some(next_id)
}
}
pub fn spawn_entity(&mut self, world: WorldId, builder: EntityBuilder) -> Option<Entity> {
let next_id = self.next_entity_id;
let mut cmd = CommandBuffer::new();
self.run_commands(&mut cmd, vec![Command::Spawn(world, builder)]);
self.submit_buffer(cmd);
let new_next = self.next_entity_id;
if next_id == new_next {
None
} else {
Some(next_id)
}
}
}
#[derive(Default)]
pub struct UniverseBuilder {
worlds: Vec<WorldBuilder>,
universe_systems: UniverseSystemStorage,
ticking_systems: TickingSystemStorage,
entity_systems: EntitySystemStorage,
component_systems: ComponentSystemStorage,
}
impl UniverseBuilder {
#[must_use]
fn new() -> Self {
Self::default()
}
#[must_use]
pub fn build(self) -> Universe {
Universe::build(self)
}
#[must_use]
pub fn with_world(mut self, builder: WorldBuilder) -> Self {
self.worlds.push(builder);
self
}
#[must_use]
pub fn with_universe_system(mut self, system: impl UniverseSystem) -> Self {
self.universe_systems.push(system);
self
}
#[must_use]
pub fn with_entity_system(mut self, system: impl EntitySystem) -> Self {
self.entity_systems.push(system);
self
}
#[must_use]
pub fn with_ticking_system(mut self, system: impl TickingSystem) -> Self {
self.ticking_systems.push(system);
self
}
#[must_use]
pub fn with_component_system(mut self, system: impl ComponentSystem) -> Self {
self.component_systems.push(system);
self
}
#[must_use]
pub fn with_other(mut self, other: Self) -> Self {
for world in other.worlds {
self.worlds.push(world);
}
for system in other.universe_systems {
self.universe_systems.push_any(system);
}
for system in other.entity_systems {
self.entity_systems.push_any(system);
}
for system in other.ticking_systems {
self.ticking_systems.push_any(system);
}
for (type_id, systems) in other.component_systems {
for system in systems {
self.component_systems.push_any(type_id, system);
}
}
self
}
}
#[cfg(test)]
mod tests;