From 60e9b14baddaa9f6f7afa85cf719543f47ecb933 Mon Sep 17 00:00:00 2001 From: hodasemi Date: Tue, 4 Mar 2025 18:29:45 +0100 Subject: [PATCH] Initial commit --- .gitignore | 2 + Cargo.toml | 17 + ecs/Cargo.toml | 19 + ecs/src/entity.rs | 313 ++++++++++++++ ecs/src/entity_object_manager.rs | 19 + ecs/src/events.rs | 88 ++++ ecs/src/lib.rs | 18 + ecs/src/resources.rs | 178 ++++++++ ecs/src/type_map.rs | 332 +++++++++++++++ ecs/src/unsafe_component_store.rs | 67 +++ ecs/src/updates.rs | 675 ++++++++++++++++++++++++++++++ ecs/src/world.rs | 362 ++++++++++++++++ scene_update_macros/Cargo.toml | 13 + scene_update_macros/src/lib.rs | 117 ++++++ 14 files changed, 2220 insertions(+) create mode 100644 .gitignore create mode 100644 Cargo.toml create mode 100644 ecs/Cargo.toml create mode 100644 ecs/src/entity.rs create mode 100644 ecs/src/entity_object_manager.rs create mode 100644 ecs/src/events.rs create mode 100644 ecs/src/lib.rs create mode 100644 ecs/src/resources.rs create mode 100644 ecs/src/type_map.rs create mode 100644 ecs/src/unsafe_component_store.rs create mode 100644 ecs/src/updates.rs create mode 100644 ecs/src/world.rs create mode 100644 scene_update_macros/Cargo.toml create mode 100644 scene_update_macros/src/lib.rs diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..869df07 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +/target +Cargo.lock \ No newline at end of file diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..4a593d0 --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,17 @@ +[workspace] +resolver = "2" + +members = ["ecs", "scene_update_macros"] + +[workspace.dependencies] +anyhow = { version = "1.0.86", features = ["backtrace"] } +destructure_traitobject = "0.3.0" +indexmap = { version = "2.2.6", features = ["rayon"] } +serde = { version = "1.0.203", features = ["derive"] } +paste = "1.0.15" +ron = "0.8.1" +syn = { version = "2.0.67", features = ["extra-traits", "full"] } +quote = "1.0.35" +proc-macro2 = "1.0.86" + +utilities = { git = "https://gavania.de/hodasemi/utilities.git" } diff --git a/ecs/Cargo.toml b/ecs/Cargo.toml new file mode 100644 index 0000000..340face --- /dev/null +++ b/ecs/Cargo.toml @@ -0,0 +1,19 @@ +[package] +name = "ecs" +version = "0.1.0" +authors = ["hodasemi "] +edition = "2024" + +[dependencies] +anyhow.workspace = true +destructure_traitobject.workspace = true +indexmap.workspace = true +serde.workspace = true +paste.workspace = true +ron.workspace = true +utilities.workspace = true + +scene_update_macros = { path = "../scene_update_macros" } + +[features] +timings = [] diff --git a/ecs/src/entity.rs b/ecs/src/entity.rs new file mode 100644 index 0000000..fdfdc35 --- /dev/null +++ b/ecs/src/entity.rs @@ -0,0 +1,313 @@ +use core::fmt; +use std::any::TypeId; +use std::collections::HashSet; +use std::{num::ParseIntError, str::FromStr}; + +use anyhow::Result; +use indexmap::IndexMap; +use serde::{Deserialize, Serialize}; + +use crate::{ComponentDebug, ComponentNotFoundError, EntityComponent, MultiMut, TypeMap}; + +#[derive(Debug)] +pub struct EntityNotFoundError { + entity: Entity, +} + +impl EntityNotFoundError { + pub(crate) fn new(entity: Entity) -> Self { + Self { entity } + } +} + +impl std::fmt::Display for EntityNotFoundError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "Entity (ID: {}) not found!", self.entity) + } +} + +impl std::error::Error for EntityNotFoundError {} + +#[derive(Default)] +pub struct ActivationState { + activated: bool, +} + +impl ActivationState { + pub fn is_active(&self) -> bool { + self.activated + } + + pub(crate) fn apply_change(&mut self) { + self.activated = !self.activated; + } +} + +pub struct EntityObject { + #[cfg(debug_assertions)] + pub debug_name: Option, + + // gltf file name + pub gltf_file: Option, + + // activation state of Entity + pub(crate) activation_state: ActivationState, + + // program local ID + pub entity_id: u32, + + // component map + pub components: TypeMap, + + #[cfg(debug_assertions)] + component_names: Vec, +} + +unsafe impl Send for EntityObject {} +unsafe impl Sync for EntityObject {} + +impl EntityObject { + pub(crate) fn new(id: u32) -> Self { + Self { + #[cfg(debug_assertions)] + debug_name: None, + + gltf_file: None, + + activation_state: ActivationState::default(), + + entity_id: id, + + components: TypeMap::default(), + + #[cfg(debug_assertions)] + component_names: Vec::new(), + } + } + + pub fn gltf_file(&self) -> Option<&String> { + self.gltf_file.as_ref() + } + + pub fn multi_mut(&mut self) -> MultiMut<'_> { + self.components.multi_mut() + } + + pub fn insert_component( + &mut self, + component: T, + ) -> Option { + assert!( + !self.activation_state.is_active(), + "inserting components while the entity is activated is not allowed" + ); + + #[cfg(debug_assertions)] + { + let name = component.name().to_string(); + + if !self.component_names.contains(&name) { + self.component_names.push(name); + } + } + + self.components.insert(component) + } + + pub(crate) fn insert_component_by_id( + &mut self, + type_id: TypeId, + component: Box, + ) -> Option> { + assert!( + !self.activation_state.is_active(), + "inserting components while the entity is activated is not allowed" + ); + + #[cfg(debug_assertions)] + { + let name = component.name().to_string(); + + if !self.component_names.contains(&name) { + self.component_names.push(name); + } + } + + self.components.insert_type(type_id, component) + } + + pub fn remove_component(&mut self) -> Option { + assert!( + !self.activation_state.is_active(), + "removing components while the entity is activated is not allowed" + ); + + let t: Option = self.components.remove(); + + #[cfg(debug_assertions)] + if let Some(t) = &t { + if let Some(index) = self + .component_names + .iter() + .position(|name| name == t.name()) + { + self.component_names.remove(index); + } + } + + t + } + + pub(crate) fn remove_component_by_id( + &mut self, + type_id: TypeId, + ) -> Option> { + assert!( + !self.activation_state.is_active(), + "removing components while the entity is activated is not allowed" + ); + + let t = self.components.remove_by_type_id(&type_id); + + #[cfg(debug_assertions)] + if let Some(t) = &t { + if let Some(index) = self + .component_names + .iter() + .position(|name| name == t.name()) + { + self.component_names.remove(index); + } + } + + t + } + + #[cfg(debug_assertions)] + pub fn component_names(&self) -> &[String] { + &self.component_names + } + + pub fn get_component( + &self, + ) -> std::result::Result<&T, ComponentNotFoundError> { + self.components.get() + } + + pub fn get_component_mut( + &mut self, + ) -> std::result::Result<&mut T, ComponentNotFoundError> { + self.components.get_mut() + } + + pub fn contains_component(&self) -> bool { + self.components.contains::() + } + + pub fn is_activated(&self) -> bool { + self.activation_state.is_active() + } + + pub fn as_entity(&self) -> Entity { + Entity { id: self.entity_id } + } + + pub(crate) fn clone_without_components(&self, id: u32) -> EntityObject { + EntityObject { + #[cfg(debug_assertions)] + debug_name: self.debug_name.clone(), + + // gltf file name + gltf_file: self.gltf_file.clone(), + + activation_state: Default::default(), + + entity_id: id, + + // component map + components: TypeMap::default(), + + #[cfg(debug_assertions)] + component_names: Vec::new(), + } + } + + pub fn clone_component_from( + &mut self, + other: &Self, + ) -> Result<()> { + let component = other.get_component::()?; + self.insert_component(component.clone()); + + Ok(()) + } +} + +#[allow(clippy::derive_hash_xor_eq)] +#[derive(Debug, Clone, Copy, Hash, Eq, Serialize, Deserialize)] +pub struct Entity { + pub(crate) id: u32, +} + +impl PartialEq for Entity { + fn eq(&self, entity: &EntityObject) -> bool { + self.id == entity.entity_id + } +} + +impl PartialEq for Entity { + fn eq(&self, entity: &Entity) -> bool { + self.id == entity.id + } +} + +impl fmt::Display for Entity { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{}", self.id) + } +} + +impl FromStr for Entity { + type Err = ParseIntError; + + fn from_str(s: &str) -> std::result::Result { + Ok(Self { id: s.parse()? }) + } +} + +pub struct EntityMultiMut<'a> { + entities: &'a mut IndexMap, + buffer: HashSet, +} + +impl<'a> EntityMultiMut<'a> { + pub(crate) fn new(entities: &'a mut IndexMap) -> Self { + Self { + entities, + buffer: HashSet::new(), + } + } + + pub fn get( + &mut self, + entity: Entity, + ) -> std::result::Result<&'a mut EntityObject, EntityNotFoundError> { + match self.entities.get_mut(&entity) { + Some(v) => { + let ptr = v as *mut EntityObject; + + assert!( + self.buffer.get(&entity).is_none(), + "Entity ({}) already borrowed", + entity.id + ); + self.buffer.insert(entity); + + let e: Option<&'a mut EntityObject> = Some(unsafe { &mut *ptr }); + + e.ok_or_else(|| EntityNotFoundError::new(entity)) + } + None => Err(EntityNotFoundError::new(entity)), + } + } +} diff --git a/ecs/src/entity_object_manager.rs b/ecs/src/entity_object_manager.rs new file mode 100644 index 0000000..e16afcd --- /dev/null +++ b/ecs/src/entity_object_manager.rs @@ -0,0 +1,19 @@ +use crate::EntityObject; + +#[derive(Default)] +pub struct EntityObjectManager { + current_entity_id: u32, +} + +impl EntityObjectManager { + pub(crate) fn fetch_add_entity_id(&mut self) -> u32 { + let id = self.current_entity_id; + self.current_entity_id += 1; + + id + } + + pub(crate) fn create_entity(&mut self) -> EntityObject { + EntityObject::new(self.fetch_add_entity_id()) + } +} diff --git a/ecs/src/events.rs b/ecs/src/events.rs new file mode 100644 index 0000000..71abc73 --- /dev/null +++ b/ecs/src/events.rs @@ -0,0 +1,88 @@ +use std::{ + any::{Any, TypeId}, + collections::HashMap, + ops::DerefMut, + sync::Arc, +}; + +use crate::World; + +pub struct Events { + events: HashMap< + TypeId, // TypeId of Payload + ( + Vec>, // Payload + Vec anyhow::Result<()> + Send + Sync>>, // Listener on Payload + ), + >, +} + +impl Default for Events { + fn default() -> Self { + Self { + events: HashMap::new(), + } + } +} + +impl Events { + pub(crate) fn take_events(&mut self) -> Self { + Self { + events: self + .events + .iter_mut() + .filter_map(|(type_id, (payload, listener))| { + (!payload.is_empty()) + .then(|| (*type_id, (std::mem::take(payload), listener.clone()))) + }) + .collect(), + } + } + + pub fn clear(&mut self) { + for (payloads, listener) in self.events.values_mut() { + payloads.clear(); + listener.clear(); + } + } + + pub fn register_event(&mut self) { + self.events + .insert(TypeId::of::(), (Vec::new(), Vec::new())); + } + + pub fn add_reader(&mut self, f: F) + where + F: Fn(&mut World, &T) -> anyhow::Result<()> + Send + Sync + 'static, + { + match self.events.get_mut(&TypeId::of::()) { + Some((_, listener)) => listener.push(Arc::new(move |world, payload| { + let typed_payload: &T = payload.downcast_ref().unwrap(); + + f(world, typed_payload) + })), + None => panic!("register event type first!"), + } + } + + pub fn write_event(&mut self, payload: T) { + match self.events.get_mut(&TypeId::of::()) { + Some((payloads, _)) => payloads.push(Box::new(payload)), + None => panic!("register event type first!"), + } + } + + pub(crate) fn fire_events(&mut self, world: &mut World) -> anyhow::Result<()> { + for (payloads, listeners) in self.events.values_mut() { + for payload in payloads.iter_mut() { + for listener in listeners.iter_mut() { + (listener)(world, payload.deref_mut())?; + } + } + + payloads.clear(); + } + + Ok(()) + } +} diff --git a/ecs/src/lib.rs b/ecs/src/lib.rs new file mode 100644 index 0000000..b74be15 --- /dev/null +++ b/ecs/src/lib.rs @@ -0,0 +1,18 @@ +mod entity; +mod entity_object_manager; +mod events; +mod resources; +mod type_map; +mod unsafe_component_store; +mod updates; +mod world; + +pub use crate::entity::{Entity, EntityMultiMut, EntityNotFoundError, EntityObject}; +pub use crate::events::Events; +pub use crate::resources::{ResourceMultiMut, Resources}; +pub use crate::type_map::{ + ComponentCreateInfo, ComponentDebug, ComponentNotFoundError, EntityComponent, MultiMut, TypeMap, +}; +pub use crate::unsafe_component_store::UnsafeComponentStore; +pub use crate::updates::*; +pub use crate::world::{World, WorldBuilder}; diff --git a/ecs/src/resources.rs b/ecs/src/resources.rs new file mode 100644 index 0000000..44fbd15 --- /dev/null +++ b/ecs/src/resources.rs @@ -0,0 +1,178 @@ +use std::{ + any::{Any, TypeId}, + collections::HashMap, + mem::transmute, +}; + +use utilities::prelude::{remove_life_time, remove_life_time_mut}; + +type Untyped = dyn Any + Send + Sync; + +#[derive(Default)] +pub struct Resources { + map: HashMap>, +} + +impl Resources { + pub fn insert(&mut self, value: T) -> Option { + self.map + .insert(TypeId::of::(), Box::new(value)) + .map(|any| *Self::downcast_unchecked(any)) + } + + pub fn insert_if_not_exists(&mut self) { + if !self.contains::() { + self.insert(T::default()); + } + } + + pub fn remove(&mut self) -> Option { + self.map + .remove(&TypeId::of::()) + .map(|any| *Self::downcast_unchecked(any)) + } + + pub fn get(&self) -> &T { + self.get_opt::().unwrap() + } + + pub fn get_by_type_id(&self, type_id: TypeId) -> &T { + self.get_opt_by_type_id(type_id).unwrap() + } + + pub fn get_unchecked<'a, T: Any + Send + Sync>(&self) -> &'a T { + unsafe { remove_life_time(self.get::()) } + } + + pub fn get_opt(&self) -> Option<&T> { + self.get_opt_by_type_id(TypeId::of::()) + } + + pub fn get_opt_by_type_id(&self, type_id: TypeId) -> Option<&T> { + debug_assert_eq!(type_id, TypeId::of::()); + + self.map + .get(&type_id) + .map(|any| Self::downcast_ref_unchecked(any)) + } + + pub fn get_mut(&mut self) -> &mut T { + self.get_mut_opt::().unwrap() + } + + pub fn get_mut_by_type_id(&mut self, type_id: TypeId) -> &mut T { + self.get_mut_opt_by_type_id(type_id).unwrap() + } + + pub fn get_mut_by_type_id_untyped(&mut self, type_id: TypeId) -> &mut Untyped { + self.get_mut_opt_by_type_id_untyped(type_id).unwrap() + } + + pub fn get_mut_unchecked<'a, T: Any + Send + Sync>(&mut self) -> &'a mut T { + unsafe { remove_life_time_mut(self.get_mut::()) } + } + + pub fn get_mut_opt(&mut self) -> Option<&mut T> { + self.get_mut_opt_by_type_id(TypeId::of::()) + } + + pub fn get_mut_opt_by_type_id( + &mut self, + type_id: TypeId, + ) -> Option<&mut T> { + debug_assert_eq!(type_id, TypeId::of::()); + + self.map + .get_mut(&type_id) + .map(|any| Self::downcast_mut_unchecked(any)) + } + + pub fn get_mut_opt_by_type_id_untyped(&mut self, type_id: TypeId) -> Option<&mut Untyped> { + self.map.get_mut(&type_id).map(|any| any.as_mut()) + } + + pub fn multi_mut(&mut self) -> ResourceMultiMut<'_> { + ResourceMultiMut::new(&mut self.map) + } + + pub fn contains(&self) -> bool { + self.map.contains_key(&TypeId::of::()) + } +} + +// helper +impl Resources { + fn downcast_unchecked(boxed: Box) -> Box { + unsafe { Box::from_raw(Box::into_raw(boxed) as *mut T) } + } + + fn downcast_ref_unchecked(boxed_ref: &Box) -> &T { + unsafe { + let ptr_to_ptr: *const *const T = + transmute(destructure_traitobject::data(boxed_ref as *const _)); + + &**ptr_to_ptr + } + } + + fn downcast_mut_unchecked(boxed_ref: &mut Box) -> &mut T { + unsafe { + let ptr_to_ptr: *mut *mut T = + transmute(destructure_traitobject::data(boxed_ref as *mut _)); + + &mut **ptr_to_ptr + } + } +} + +/// Allows mutable access to multiple components at once +pub struct ResourceMultiMut<'a> { + map: &'a mut HashMap>, + buffer: Vec<*mut Box>, +} + +impl<'a> ResourceMultiMut<'a> { + fn new(map: &'a mut HashMap>) -> Self { + ResourceMultiMut { + map, + buffer: Vec::new(), + } + } + + /// Returns requested type on success + pub fn get(&mut self) -> &'a mut T { + self.get_by_type_id(&TypeId::of::()) + .map(|component| Resources::downcast_mut_unchecked(component)) + .unwrap() + } + + /// Returns requested type behind this type id on success + pub fn get_by_type_id( + &mut self, + type_id: &TypeId, + ) -> Option<&'a mut Box> { + self.map.get_mut(type_id).map(|v| { + let ptr = v as *mut _; + + match self.buffer.iter().find(|v| **v == ptr) { + Some(_) => { + panic!("This key has already been borrowed!"); + } + None => { + self.buffer.push(ptr); + } + } + + let t: Option<&'a mut Box> = unsafe { transmute(ptr) }; + + t.unwrap() + }) + } + + /// # Safety + /// + /// use this only when there are no references left + pub unsafe fn clear_all_usages(&mut self) { + self.buffer.clear(); + } +} diff --git a/ecs/src/type_map.rs b/ecs/src/type_map.rs new file mode 100644 index 0000000..3dfb1a1 --- /dev/null +++ b/ecs/src/type_map.rs @@ -0,0 +1,332 @@ +use std::mem::transmute; +use std::{ + any::{Any, TypeId}, + collections::{ + HashMap, + hash_map::{Iter, IterMut}, + }, + fmt::Display, +}; + +use anyhow::Result; +use destructure_traitobject; +use serde::{Deserialize, Serialize}; + +use crate::World; + +pub trait EntityComponent: Any + Send + Sync { + fn enable(&mut self, _world: &mut World) -> Result<()> { + Ok(()) + } + + fn disable(&mut self, _world: &mut World) -> Result<()> { + Ok(()) + } + + fn name(&self) -> &str; +} + +pub trait ComponentDebug { + fn debug_name() -> &'static str; +} + +pub trait ComponentCreateInfo<'de>: Serialize + Deserialize<'de> { + fn to_string(&self) -> Result { + Ok(ron::to_string(self)?) + } + + fn from_str(s: &'de str) -> Option { + match ron::from_str(s) { + Ok(t) => Some(t), + Err(err) => { + println!("{:?}", err); + + None + } + } + } +} + +pub struct TypeMap { + map: HashMap>, +} + +impl Default for TypeMap { + fn default() -> Self { + Self { + map: HashMap::new(), + } + } +} + +impl TypeMap { + pub fn insert(&mut self, value: T) -> Option { + self.map + .insert(TypeId::of::(), Box::new(value)) + .map(|any| *Self::downcast_unchecked(any)) + } + + pub fn insert_type( + &mut self, + type_id: TypeId, + component: Box, + ) -> Option> { + self.map.insert(type_id, component) + } + + pub fn remove(&mut self) -> Option { + self.remove_by_type_id(&TypeId::of::()) + .map(|any| *Self::downcast_unchecked(any)) + } + + pub fn remove_by_type_id(&mut self, type_id: &TypeId) -> Option> { + self.map.remove(type_id) + } + + pub fn get( + &self, + ) -> std::result::Result<&T, ComponentNotFoundError> { + self.map + .get(&TypeId::of::()) + .map(|any| Self::downcast_ref_unchecked(any)) + .ok_or_else(ComponentNotFoundError::component::) + } + + pub fn get_by_type_id( + &self, + type_id: &TypeId, + ) -> std::result::Result<&Box, ComponentNotFoundError> { + self.map + .get(type_id) + .ok_or_else(|| ComponentNotFoundError::type_id(*type_id)) + } + + pub fn get_mut( + &mut self, + ) -> std::result::Result<&mut T, ComponentNotFoundError> { + self.map + .get_mut(&TypeId::of::()) + .map(|any| Self::downcast_mut_unchecked(any)) + .ok_or_else(ComponentNotFoundError::component::) + } + + pub fn get_mut_by_type_id( + &mut self, + type_id: &TypeId, + ) -> std::result::Result<&mut Box, ComponentNotFoundError> { + self.map + .get_mut(type_id) + .ok_or_else(|| ComponentNotFoundError::type_id(*type_id)) + } + + pub fn contains(&self) -> bool { + self.contains_type_id(&TypeId::of::()) + } + + pub fn contains_type_id(&self, type_id: &TypeId) -> bool { + self.map.contains_key(type_id) + } + + pub fn multi_mut(&mut self) -> MultiMut<'_> { + MultiMut::new(&mut self.map) + } + + pub fn iter(&self) -> Iter<'_, TypeId, Box> { + self.map.iter() + } + + pub fn iter_mut(&mut self) -> IterMut<'_, TypeId, Box> { + self.map.iter_mut() + } + + fn downcast_unchecked(boxed: Box) -> Box { + unsafe { Box::from_raw(Box::into_raw(boxed) as *mut T) } + } + + pub fn downcast_ref_unchecked(boxed_ref: &Box) -> &T { + unsafe { + let ptr_to_ptr: *const *const T = + transmute(destructure_traitobject::data(boxed_ref as *const _)); + + &**ptr_to_ptr + } + } + + pub fn downcast_mut_unchecked( + boxed_ref: &mut Box, + ) -> &mut T { + unsafe { + let ptr_to_ptr: *mut *mut T = + transmute(destructure_traitobject::data(boxed_ref as *mut _)); + + &mut **ptr_to_ptr + } + } +} + +/// Allows mutable access to multiple components at once +pub struct MultiMut<'a> { + map: &'a mut HashMap>, + buffer: Vec<*mut Box>, +} + +impl<'a> MultiMut<'a> { + fn new(map: &'a mut HashMap>) -> Self { + MultiMut { + map, + buffer: Vec::new(), + } + } + + /// Returns requested type on success + pub fn get( + &mut self, + ) -> std::result::Result<&'a mut T, ComponentNotFoundError> { + self.get_by_type_id(&TypeId::of::()) + .map(|component| TypeMap::downcast_mut_unchecked(component)) + .map_err(|_| ComponentNotFoundError::component::()) + } + + /// Returns requested type behind this type id on success + pub fn get_by_type_id( + &mut self, + type_id: &TypeId, + ) -> std::result::Result<&'a mut Box, ComponentNotFoundError> { + match self.map.get_mut(type_id) { + Some(v) => { + let ptr = v as *mut _; + + match self.buffer.iter().find(|v| **v == ptr) { + Some(_) => { + panic!("This key has already been borrowed!"); + } + None => { + self.buffer.push(ptr); + } + } + + let t: Option<&'a mut Box> = unsafe { transmute(ptr) }; + + t.ok_or_else(|| ComponentNotFoundError::type_id(*type_id)) + } + None => Err(ComponentNotFoundError::type_id(*type_id)), + } + } + + /// # Safety + /// + /// use this only when there are no references left + pub unsafe fn clear_all_usages(&mut self) { + self.buffer.clear(); + } +} + +#[derive(Debug)] +pub enum ComponentRequestType { + TypeId(TypeId), + ComponentName(&'static str), +} + +#[derive(Debug)] +pub struct ComponentNotFoundError { + pub request_type: ComponentRequestType, +} + +impl ComponentNotFoundError { + fn type_id(type_id: TypeId) -> Self { + Self { + request_type: ComponentRequestType::TypeId(type_id), + } + } + + fn component() -> Self { + Self { + request_type: ComponentRequestType::ComponentName(T::debug_name()), + } + } +} + +impl Display for ComponentNotFoundError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "Entity Component ({:?}) not found!", self.request_type) + } +} + +impl std::error::Error for ComponentNotFoundError {} + +#[derive(Debug, Default, Clone, Serialize, Deserialize)] +struct Test { + x: u32, + y: u32, + z: u32, +} + +impl EntityComponent for Test { + fn name(&self) -> &str { + "Test" + } +} + +impl ComponentDebug for Test { + fn debug_name() -> &'static str { + "Test" + } +} + +impl PartialEq for Test { + fn eq(&self, other: &Self) -> bool { + self.x == other.x && self.y == other.y && self.z == other.z + } +} + +#[test] +fn verify_multi_mut() { + let mut map = TypeMap::default(); + + map.insert(Test::default()); + + // test it multiple times, just for sanity + for _ in 0..10 { + let test = map.get::().unwrap().clone(); + + let mut multi_mut = map.multi_mut(); + + let multi_mut_test = multi_mut.get::().unwrap().clone(); + + assert_eq!(test, multi_mut_test); + } +} + +#[test] +fn verify_insert() { + let mut map = TypeMap::default(); + + let reference = Test { x: 5, y: 20, z: 30 }; + + map.insert(reference.clone()); + let old = map.insert(reference.clone()).unwrap(); + + assert_eq!(old, reference); +} + +#[test] +fn verify_get() { + let mut map = TypeMap::default(); + + let reference = Test { x: 5, y: 20, z: 30 }; + + map.insert(reference.clone()); + + assert_eq!(map.get::().unwrap().clone(), reference); +} + +#[test] +fn verify_get_mut() { + let mut map = TypeMap::default(); + + let reference = Test { x: 5, y: 20, z: 30 }; + + map.insert(reference.clone()); + + assert_eq!(map.get_mut::().unwrap().clone(), reference); +} diff --git a/ecs/src/unsafe_component_store.rs b/ecs/src/unsafe_component_store.rs new file mode 100644 index 0000000..cdf17af --- /dev/null +++ b/ecs/src/unsafe_component_store.rs @@ -0,0 +1,67 @@ +use std::{fmt, ptr}; + +#[derive(Clone)] +pub struct UnsafeComponentStore { + ptr: *mut T, +} + +impl UnsafeComponentStore { + /// # Safety + /// + /// Creates structure with null pointer. Should never be called like this. + pub unsafe fn empty() -> Self { + Self { + ptr: ptr::null_mut(), + } + } + + /// # Safety + /// + /// Returns mutable reference to the inner pointer. Use always with caution + /// and make 100% sure that the actual struct is still present. + #[allow(clippy::mut_from_ref)] + pub unsafe fn as_mut(&self) -> &mut T { + assert_ne!( + self.ptr, + ptr::null_mut(), + "Called UnsafeComponentStore while being empty" + ); + + unsafe { self.ptr.as_mut().unwrap() } + } + + pub fn is_init(&self) -> bool { + !self.ptr.is_null() + } +} + +impl From<&mut T> for UnsafeComponentStore { + fn from(ptr: &mut T) -> Self { + Self { ptr: ptr as *mut T } + } +} + +impl From<&T> for UnsafeComponentStore { + fn from(ptr: &T) -> Self { + Self { + ptr: ptr as *const T as *mut T, + } + } +} + +impl fmt::Debug for UnsafeComponentStore { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("UnsafeComponentStore") + .field("ptr", &self.ptr) + .finish() + } +} + +impl Default for UnsafeComponentStore { + fn default() -> Self { + unsafe { Self::empty() } + } +} + +unsafe impl Send for UnsafeComponentStore {} +unsafe impl Sync for UnsafeComponentStore {} diff --git a/ecs/src/updates.rs b/ecs/src/updates.rs new file mode 100644 index 0000000..638b324 --- /dev/null +++ b/ecs/src/updates.rs @@ -0,0 +1,675 @@ +#![allow(clippy::type_complexity)] + +use std::any::TypeId; + +use std::marker::PhantomData; +#[cfg(feature = "timings")] +use std::time::Instant; + +use anyhow::Result; +use indexmap::IndexMap; + +#[cfg(feature = "timings")] +use super::super::timings::Timings; + +use crate::*; +use scene_update_macros::implement_pair_update; + +macro_rules! impl_singleton_update { + ( $name: ident, $([$var: ident]$(,)?)+ ) => { + impl Archetype { + paste::item! { + pub fn [](f: F, filter: Filter) -> Self + where + F: Fn(&mut World, Entity, $(&mut $var,)+) -> Result<()> + Send + Sync + Clone + 'static, + Filter: CheckFilter + 'static, + $( + $var: EntityComponent + ComponentDebug, + )+ + { + $( + filter.verify_dedup::<$var>(); + )+ + + Self { + check_entity: Box::new({ + move |entity| { + $( + if !entity.components.contains::<$var>() { + return false; + } + )+ + + if !filter.check(entity) { + return false; + } + + true + } + }), + + create_callback: Box::new(move |entity| { + $( + let [< $var:lower >] = UnsafeComponentStore::from( + entity.get_component::<$var>()? + ); + )+ + + let f = f.clone(); + + Ok(Box::new(move |e, scene_contents| { + unsafe { f(scene_contents, e, $([< $var:lower >].as_mut(),)+) } + })) + }), + + entities: IndexMap::new(), + } + } + } + } + + impl AddUpdates2<( $( $var, )+ ), Func, Filter> for Updates + where + $( + $var: EntityComponent + ComponentDebug, + )+ + Func: Fn(& mut World, Entity, $(&mut $var,)+) -> Result<()> + Send + Sync + Clone + 'static, + Filter: CheckFilter + 'static + { + fn add_update( + &mut self, + name: &str, + priority: u32, + func: Func, + filter: Filter + ) -> Result<()> { + paste::item! { + self.add(name, priority, Update::Single(Archetype::[](func, filter))) + } + } + } + + impl AddUpdates<( $( $var, )+ ), Func, Filter> for WorldBuilder + where + $( + $var: EntityComponent + ComponentDebug, + )+ + Func: Fn(& mut World, Entity, $(&mut $var,)+) -> Result<()> + Send + Sync + Clone + 'static, + Filter: CheckFilter + 'static + { + fn add_update( + &mut self, + name: &str, + priority: u32, + func: Func, + filter: Filter, + ) -> Result<()> { + self.updates.add_update(name, priority, func, filter) + } + } + }; +} + +macro_rules! impl_pair_update { + ( + $lhs_id: expr, + ( $([$lhs_little: ident: $lhs_big: ident]$(,)?)+ ), + $rhs_id: expr, + ( $([$rhs_little: ident: $rhs_big: ident]$(,)?)+ ) + ) => { + impl ArchetypePair { + paste::item! { + pub fn [] + (f: F, left_filter: LeftFilter, right_filter: RightFilter) -> Self + where + F: Fn(&mut World, (Entity, $(&mut $lhs_big,)+), (Entity, $(&mut $rhs_big,)+)) -> Result<()> + Send + Sync + Clone + 'static, + LeftFilter: CheckFilter + 'static, + RightFilter: CheckFilter + 'static, + $( + $rhs_big: EntityComponent + ComponentDebug, + )+ + $( + $lhs_big: EntityComponent + ComponentDebug, + )+ + { + $( + left_filter.verify_dedup::<$lhs_big>(); + )+ + + $( + right_filter.verify_dedup::<$rhs_big>(); + )+ + + Self { + check_left_entity: Box::new({ + move |entity| { + $( + if !entity.components.contains::<$lhs_big>() { + return false; + } + )+ + + if !left_filter.check(entity) { + return false; + } + + true + } + }), + check_right_entity: Box::new({ + move |entity| { + $( + if !entity.components.contains::<$rhs_big>() { + return false; + } + )+ + + if !right_filter.check(entity) { + return false; + } + + true + } + }), + + create_callback: Box::new(move |lhs_entity, rhs_entity| { + $( + let $lhs_little = UnsafeComponentStore::from( + lhs_entity.get_component::<$lhs_big>()? + ); + )+ + + $( + let $rhs_little = UnsafeComponentStore::from( + rhs_entity.get_component::<$rhs_big>()? + ); + )+ + + let f = f.clone(); + + Ok(Box::new(move |lhs_e, rhs_e, scene_contents| { + unsafe { f(scene_contents, (lhs_e, $($lhs_little.as_mut(),)+), (rhs_e, $($rhs_little.as_mut(),)+) ) } + })) + }), + + entities: IndexMap::new(), + } + } + } + } + + impl AddUpdates2<( ($( $lhs_big, )+), ($($rhs_big,)+) ), Func, (LhsFilter, RhsFilter)> for Updates + where + $( + $rhs_big: EntityComponent + ComponentDebug, + )+ + $( + $lhs_big: EntityComponent + ComponentDebug, + )+ + Func: Fn(& mut World, (Entity, $(&mut $lhs_big,)+), (Entity, $(&mut $rhs_big,)+)) -> Result<()> + Send + Sync + Clone + 'static, + LhsFilter: CheckFilter + 'static, + RhsFilter: CheckFilter + 'static + { + fn add_update( + &mut self, + name: &str, + priority: u32, + func: Func, + filter: (LhsFilter, RhsFilter) + ) -> Result<()> { + paste::item! { + self.add(name, priority, Update::Pair(ArchetypePair::[](func, filter.0, filter.1)))?; + } + + Ok(()) + } + } + + impl AddUpdates<( ($( $lhs_big, )+), ($($rhs_big,)+) ), Func, (LhsFilter, RhsFilter)> for WorldBuilder + where + $( + $rhs_big: EntityComponent + ComponentDebug, + )+ + $( + $lhs_big: EntityComponent + ComponentDebug, + )+ + Func: Fn(& mut World, (Entity, $(&mut $lhs_big,)+), (Entity, $(&mut $rhs_big,)+)) -> Result<()> + Send + Sync + Clone + 'static, + LhsFilter: CheckFilter + 'static, + RhsFilter: CheckFilter + 'static + { + fn add_update( + &mut self, + name: &str, + priority: u32, + func: Func, + filter: (LhsFilter, RhsFilter), + ) -> Result<()> { + self.updates.add_update(name, priority, func, filter) + } + } + }; +} + +macro_rules! impl_update_filter { + ( $name: ident, $([$big: ident, $little: ident]$(,)?)+ ) => { + paste::item! { + pub struct [<$name Filter>]<$($big: EntityComponent,)+> { + $( + $little: std::marker::PhantomData<$big>, + )+ + } + + impl<$($big: EntityComponent,)+> Default for [<$name Filter>]<$($big,)+> { + fn default() -> Self { + Self { + $( + $little: std::marker::PhantomData, + )+ + } + } + } + + impl<$($big: EntityComponent,)+> CheckFilter for [<$name Filter>]<$($big,)+> { + fn check(&self, entity: &EntityObject) -> bool { + $( + if entity.contains_component::<$big>() { + return false; + } + )+ + + true + } + + fn verify_dedup(&self) { + $( + if TypeId::of::() == TypeId::of::<$big>() { + panic!("Type is used as input and filter at the same time"); + } + )+ + } + } + + impl<$($big: EntityComponent,)+> Clone for [<$name Filter>]<$($big,)+> { + fn clone(&self) -> Self { + Self { + $( + $little: self.$little.clone(), + )+ + } + } + } + } + }; +} + +pub struct Query +where + F: CheckFilter, +{ + pub components: T, + + d: PhantomData, +} + +pub trait AddUpdates { + fn add_update(&mut self, name: &str, priority: u32, func: Func, filter: Filter) -> Result<()>; +} + +trait AddUpdates2 { + fn add_update(&mut self, name: &str, priority: u32, func: Func, filter: Filter) -> Result<()>; +} + +pub trait CheckFilter: Send + Sync + Default + Clone { + fn check(&self, entity: &EntityObject) -> bool; + fn verify_dedup(&self); +} + +#[derive(Default, Clone)] +pub struct EmptyFilter; + +impl CheckFilter for EmptyFilter { + fn check(&self, _entity: &EntityObject) -> bool { + true + } + + fn verify_dedup(&self) {} +} + +#[derive(Default, Clone, Debug)] +pub struct ArchetypeInfo { + entities: Vec<(Entity, Option)>, +} + +impl ArchetypeInfo { + pub fn new(entities: Vec<(Entity, Option)>) -> Self { + Self { entities } + } + + pub fn entities(&self) -> &[(Entity, Option)] { + &self.entities + } + + pub fn count(&self) -> usize { + self.entities.len() + } +} + +pub struct Archetype { + check_entity: Box bool + Send + Sync>, + create_callback: Box< + dyn Fn(&EntityObject) -> Result Result<()> + Send + Sync>> + + Send + + Sync, + >, + + entities: IndexMap Result<()> + Send + Sync>>, +} + +impl Archetype { + pub fn add_entity(&mut self, entity_object: &EntityObject) -> Result<()> { + if (self.check_entity)(entity_object) { + let cb = (self.create_callback)(entity_object)?; + + self.entities.insert(entity_object.as_entity(), cb); + } + + Ok(()) + } + + pub fn remove_entity(&mut self, entity: Entity) { + self.entities.swap_remove(&entity); + } + + pub fn execute(&self, scene_contents: &mut World) -> Result<()> { + for (entity, callback) in self.entities.iter() { + callback(*entity, scene_contents)?; + } + + Ok(()) + } + + pub fn entities( + &self, + ) -> &IndexMap Result<()> + Send + Sync>> { + &self.entities + } +} + +pub struct ArchetypePair { + check_left_entity: Box bool + Send + Sync>, + check_right_entity: Box bool + Send + Sync>, + + create_callback: Box< + dyn Fn( + &EntityObject, + &EntityObject, + ) + -> Result Result<()> + Send + Sync>> + + Send + + Sync, + >, + + entities: IndexMap< + (Entity, Entity), + Box Result<()> + Send + Sync>, + >, +} + +impl ArchetypePair { + pub(crate) fn add_entity( + &mut self, + entity_object: &EntityObject, + entities: &IndexMap, + ) -> Result<()> { + for (other_entity, other_entity_object) in entities.iter() { + if entity_object.as_entity() == *other_entity { + continue; + } + + // check if the entities can be on both sides + if (self.check_left_entity)(entity_object) + && (self.check_right_entity)(other_entity_object) + { + let cb = (self.create_callback)(entity_object, other_entity_object)?; + + self.entities + .insert((entity_object.as_entity(), *other_entity), cb); + } + + if (self.check_left_entity)(other_entity_object) + && (self.check_right_entity)(entity_object) + { + let cb = (self.create_callback)(other_entity_object, entity_object)?; + + self.entities + .insert((*other_entity, entity_object.as_entity()), cb); + } + } + + Ok(()) + } + + pub(crate) fn remove_entity(&mut self, entity: Entity) { + while let Some((left_entity, right_entity)) = self + .entities + .keys() + .find(|(left_entity, right_entity)| *left_entity == entity || *right_entity == entity) + .cloned() + { + self.entities.swap_remove(&(left_entity, right_entity)); + } + } + + pub(crate) fn execute(&self, scene_contents: &mut World) -> Result<()> { + for ((left_entity, right_entity), callback) in self.entities.iter() { + callback(*left_entity, *right_entity, scene_contents)?; + } + + Ok(()) + } +} + +pub enum Update { + Single(Archetype), + Pair(ArchetypePair), +} + +impl From for Update { + fn from(archetype: Archetype) -> Self { + Self::Single(archetype) + } +} + +impl From for Update { + fn from(pair: ArchetypePair) -> Self { + Self::Pair(pair) + } +} + +pub struct Updates { + #[cfg(feature = "timings")] + timings: Timings, + + updates: Vec<(String, u32, Update)>, +} + +impl Default for Updates { + fn default() -> Self { + Self { + #[cfg(feature = "timings")] + timings: Timings::default, + + updates: Vec::new(), + } + } +} + +impl Updates { + pub(crate) fn update(&mut self, world: &mut World) -> Result<()> { + #[cfg(feature = "timings")] + if let Some(timings) = self.timings.check_timing(world.now(), None) { + if !timings.is_empty() { + println!("timings: {:#?}", timings); + } + } + + #[cfg(feature = "timings")] + let timings = &mut self.timings; + + self.updates + .iter() + .try_for_each(|(_name, _, update)| -> Result<()> { + #[cfg(feature = "timings")] + let before = Instant::now(); + + match update { + Update::Single(archetype) => { + archetype.execute(world)?; + } + Update::Pair(archetype_pair) => { + archetype_pair.execute(world)?; + } + } + + #[cfg(feature = "timings")] + timings.add(_name, Instant::now().duration_since(before)); + + Ok(()) + })?; + + Ok(()) + } + + pub(crate) fn add_entity( + &mut self, + entity_object: &EntityObject, + entities: &IndexMap, + ) -> Result<()> { + for (_, _, update) in self.updates.iter_mut() { + match update { + Update::Single(archetype) => { + archetype.add_entity(entity_object)?; + } + Update::Pair(archetype_pair) => { + archetype_pair.add_entity(entity_object, entities)?; + } + } + } + + Ok(()) + } + + pub(crate) fn remove_entity(&mut self, entity: Entity) { + for (_, _, update) in self.updates.iter_mut() { + match update { + Update::Single(archetype) => { + archetype.remove_entity(entity); + } + Update::Pair(archetype_pair) => { + archetype_pair.remove_entity(entity); + } + } + } + } + + // pub(crate) fn clear(&mut self) { + // self.updates.clear(); + + // #[cfg(feature = "timings")] + // self.timings.clear(); + // } + + pub(crate) fn add(&mut self, name: &str, priority: u32, update: Update) -> Result<()> { + #[cfg(feature = "timings")] + self.timings.add_timing_afterwards(name); + + self.updates.push((name.to_string(), priority, update)); + self.updates + .sort_by(|(_, lhs_prio, _), (_, rhs_prio, _)| lhs_prio.cmp(rhs_prio)); + + Ok(()) + } +} + +// #[derive(Default)] +// pub struct Archetypes { +// archetypes: HashMap, +// } + +// impl Archetypes { +// pub(crate) fn add_entity(&mut self, entity_object: &EntityObject) -> Result<()> { +// for archetype in self.archetypes.values_mut() { +// archetype.add_entity(entity_object)?; +// } + +// Ok(()) +// } + +// pub(crate) fn remove_entity(&mut self, entity: Entity) { +// for archetype in self.archetypes.values_mut() { +// archetype.remove_entity(entity); +// } +// } + +// pub(crate) fn clear(&mut self) { +// self.archetypes.clear(); +// } + +// pub(crate) fn insert(&mut self, name: String, archetype: Archetype) { +// assert!(self.archetypes.insert(name, archetype).is_none()); +// } + +// pub(crate) fn get(&self, name: &String) -> Option<&Archetype> { +// self.archetypes.get(name) +// } + +// pub(crate) fn execute( +// &self, +// name: &String, +// scene_contents: &mut SceneContents<'_>, +// ) -> Result<()> { +// self.archetypes[name].execute(scene_contents) +// } +// } + +#[rustfmt::skip] +impl_singleton_update!(single, [R]); +#[rustfmt::skip] +impl_singleton_update!(double, [R], [S]); +#[rustfmt::skip] +impl_singleton_update!(triple, [R], [S], [T]); +#[rustfmt::skip] +impl_singleton_update!(quadruple, [R], [S], [T], [U]); +#[rustfmt::skip] +impl_singleton_update!(quintuple, [R], [S], [T], [U], [V]); +#[rustfmt::skip] +impl_singleton_update!(sextuple, [R], [S], [T], [U], [V], [W]); +#[rustfmt::skip] +impl_singleton_update!(septuple, [R], [S], [T], [U], [V], [W], [X]); +#[rustfmt::skip] +impl_singleton_update!(octuple, [R], [S], [T], [U], [V], [W], [X], [Y]); +#[rustfmt::skip] +impl_singleton_update!(ninetuple, [R], [S], [T], [U], [V], [W], [X], [Y], [Z]); + +implement_pair_update!(impl_pair_update, 1, 10); + +#[rustfmt::skip] +impl_update_filter!(Monuple, [R, r]); +#[rustfmt::skip] +impl_update_filter!(Couple, [R, r], [S, s]); +#[rustfmt::skip] +impl_update_filter!(Triple, [R, r], [S, s], [T, t]); +#[rustfmt::skip] +impl_update_filter!(Quadruple, [R, r], [S, s], [T, t], [U, u]); +#[rustfmt::skip] +impl_update_filter!(Quintuple, [R, r], [S, s], [T, t], [U, u], [V, v]); +#[rustfmt::skip] +impl_update_filter!(Sextuple, [R, r], [S, s], [T, t], [U, u], [V, v], [W, w]); +#[rustfmt::skip] +impl_update_filter!(Septuple, [R, r], [S, s], [T, t], [U, u], [V, v], [W, w], [X, x]); +#[rustfmt::skip] +impl_update_filter!(Octuple, [R, r], [S, s], [T, t], [U, u], [V, v], [W, w], [X, x], [Y, y]); +#[rustfmt::skip] +impl_update_filter!(Nonuple, [R, r], [S, s], [T, t], [U, u], [V, v], [W, w], [X, x], [Y, y], [Z, z]); +#[rustfmt::skip] +impl_update_filter!(Decuple, [R, r], [S, s], [T, t], [U, u], [V, v], [W, w], [X, x], [Y, y], [Z, z], [A, a]); diff --git a/ecs/src/world.rs b/ecs/src/world.rs new file mode 100644 index 0000000..cbb648e --- /dev/null +++ b/ecs/src/world.rs @@ -0,0 +1,362 @@ +use std::{ + any::TypeId, + collections::HashMap, + time::{Duration, Instant}, +}; + +use anyhow::{Result, bail}; +use indexmap::IndexMap; +use utilities::prelude::{remove_life_time, remove_life_time_mut}; + +use crate::{entity_object_manager::EntityObjectManager, *}; + +pub struct WorldBuilder { + pub(crate) updates: Updates, + pub events: Events, + pub resources: Resources, + systems: Vec Result + Send + Sync + 'static>>, +} + +impl WorldBuilder { + pub fn add_system(&mut self, f: F) + where + F: Fn(&mut World) -> Result + Send + Sync + 'static, + { + self.systems.push(Box::new(f)); + } +} + +impl WorldBuilder { + pub fn build(self) -> World { + World { + updates: self.updates, + events: self.events, + resources: self.resources, + entities: Default::default(), + + entities_to_remove: Default::default(), + entities_to_add: Default::default(), + entities_updates: Default::default(), + + entity_object_manager: Default::default(), + + start_time: Instant::now(), + + systems: self.systems, + } + } +} + +enum ComponentChange { + Added(TypeId, Box), + Removed(TypeId), +} + +pub struct World { + pub(crate) updates: Updates, + pub events: Events, + pub resources: Resources, + pub(crate) entities: IndexMap, + + entities_to_remove: Vec, + entities_to_add: Vec, + entities_updates: HashMap>, + + entity_object_manager: EntityObjectManager, + + start_time: Instant, + + systems: Vec Result + Send + Sync + 'static>>, +} + +impl World { + pub fn builder() -> WorldBuilder { + WorldBuilder { + updates: Default::default(), + events: Default::default(), + resources: Default::default(), + systems: Default::default(), + } + } + + pub fn now(&self) -> Duration { + self.start_time.elapsed() + } + + pub fn new_entity(&mut self) -> EntityObject { + self.entity_object_manager.create_entity() + } + + pub fn clone_without_components(&mut self, entity: &EntityObject) -> EntityObject { + entity.clone_without_components(self.entity_object_manager.fetch_add_entity_id()) + } + + pub fn entities(&self) -> impl Iterator { + self.entities.values() + } + + pub fn entity( + &self, + entity: Entity, + ) -> std::result::Result<&EntityObject, EntityNotFoundError> { + self.entities + .get(&entity) + .ok_or_else(|| EntityNotFoundError::new(entity)) + } + + pub unsafe fn entity_unchecked<'a>( + &self, + entity: Entity, + ) -> std::result::Result<&'a EntityObject, EntityNotFoundError> { + self.entity(entity).map(|e| unsafe { remove_life_time(e) }) + } + + pub fn entity_mut( + &mut self, + entity: Entity, + ) -> std::result::Result<&mut EntityObject, EntityNotFoundError> { + self.entities + .get_mut(&entity) + .ok_or_else(|| EntityNotFoundError::new(entity)) + } + + pub unsafe fn entity_mut_unchecked<'a>( + &mut self, + entity: Entity, + ) -> std::result::Result<&'a mut EntityObject, EntityNotFoundError> { + self.entity_mut(entity) + .map(|e| unsafe { remove_life_time_mut(e) }) + } + + pub fn entities_multi_mut(&mut self) -> EntityMultiMut<'_> { + EntityMultiMut::new(&mut self.entities) + } + + pub fn add_entity(&mut self, entity_object: EntityObject) -> Result { + let entity = entity_object.as_entity(); + self.entities_to_add.push(entity_object); + + Ok(entity) + } + + pub fn insert_component( + &mut self, + entity: Entity, + component: T, + ) -> Result<()> { + println!("insert component {}", T::debug_name()); + + let change = ComponentChange::Added(TypeId::of::(), Box::new(component)); + + match self.entities_updates.get_mut(&entity) { + Some(changes) => { + changes.push(change); + } + None => { + self.entities_updates.insert(entity, vec![change]); + } + } + + Ok(()) + } + + pub fn remove_component( + &mut self, + entity: Entity, + ) -> Result<()> { + println!("remove component {}", T::debug_name()); + + let change = ComponentChange::Removed(TypeId::of::()); + + match self.entities_updates.get_mut(&entity) { + Some(changes) => { + changes.push(change); + } + None => { + self.entities_updates.insert(entity, vec![change]); + } + } + + Ok(()) + } + + pub fn remove_entity(&mut self, entity: Entity) -> Result> { + self.entities_to_remove.push(entity); + + Ok(None) + } +} + +// async application of changes +impl World { + fn _add_entity(&mut self, mut entity: EntityObject) -> Result { + // call enable event for all components + for (_, component) in entity.components.iter_mut() { + component.enable(self)?; + } + + entity.activation_state.apply_change(); + + let e = entity.as_entity(); + + self.updates.add_entity(&mut entity, &self.entities)?; + assert!(self.entities.insert(e, entity).is_none()); + + Ok(e) + } + + fn _remove_entity(&mut self, entity: Entity) -> Result> { + if let Some(mut entity_object) = self.entities.swap_remove(&entity) { + for (_, component) in entity_object.components.iter_mut() { + component.disable(self)?; + } + + entity_object.activation_state.apply_change(); + self.updates.remove_entity(entity); + + return Ok(Some(entity_object)); + } + + Ok(None) + } + + fn _insert_component( + &mut self, + entity: Entity, + mut component: T, + ) -> Result<()> { + let entities = unsafe { remove_life_time_mut(&mut self.entities) }; + + let entity_object = entities + .get_mut(&entity) + .ok_or_else(|| EntityNotFoundError::new(entity))?; + + self.updates.remove_entity(entity); + + entity_object.activation_state.apply_change(); + + component.enable(self)?; + if entity_object.insert_component(component).is_some() { + bail!("component {} already present", T::debug_name()); + } + + entity_object.activation_state.apply_change(); + + self.updates.add_entity(entity_object, &self.entities)?; + + Ok(()) + } + + fn _remove_component( + &mut self, + entity: Entity, + ) -> Result<()> { + let entities = unsafe { remove_life_time_mut(&mut self.entities) }; + + let entity_object = entities + .get_mut(&entity) + .ok_or_else(|| EntityNotFoundError::new(entity))?; + + self.updates.remove_entity(entity); + + entity_object.activation_state.apply_change(); + + if let Some(mut component) = entity_object.remove_component_by_id(TypeId::of::()) { + component.disable(self)?; + } + + entity_object.activation_state.apply_change(); + + self.updates.add_entity(entity_object, &self.entities)?; + + Ok(()) + } + + fn commit_entity_changes(&mut self) -> Result<()> { + if !self.entities_to_remove.is_empty() { + println!("entities to remove {}", self.entities_to_remove.len()); + } + + for entity in core::mem::take(&mut self.entities_to_remove) { + self._remove_entity(entity)?; + } + + if !self.entities_to_add.is_empty() { + println!("entities to add {}", self.entities_to_add.len()); + } + + for entity_object in core::mem::take(&mut self.entities_to_add) { + self._add_entity(entity_object)?; + } + + if !self.entities_updates.is_empty() { + println!("entities to update {}", self.entities_updates.len()); + } + + for (entity, changes) in core::mem::take(&mut self.entities_updates) { + self.updates.remove_entity(entity); + + if let Some(entity_object) = unsafe { self.entity_mut_unchecked(entity).ok() } { + entity_object.activation_state.apply_change(); + + for change in changes { + match change { + ComponentChange::Added(type_id, mut component) => { + component.enable(self)?; + let name = component.name().to_string(); + + if entity_object + .insert_component_by_id(type_id, component) + .is_some() + { + bail!("component {name} already present"); + } + } + ComponentChange::Removed(type_id) => { + if let Some(mut component) = + entity_object.remove_component_by_id(type_id) + { + component.disable(self)?; + } + } + } + } + + entity_object.activation_state.apply_change(); + + self.updates.add_entity(entity_object, &self.entities)?; + } + } + + Ok(()) + } +} + +impl World { + pub fn run(&mut self) -> Result<()> { + let systems = std::mem::take(&mut self.systems); + + loop { + // we need to take all events because of borrowing rules + let mut events = self.events.take_events(); + events.fire_events(self)?; + + { + // actually safe: + // - updates can't be altered on a running world + // - entity changes are processed afterwards + let w = unsafe { remove_life_time_mut(self) }; + + self.updates.update(w)?; + } + + self.commit_entity_changes()?; + + for system in systems.iter() { + if !system(self)? { + return Ok(()); + } + } + } + } +} diff --git a/scene_update_macros/Cargo.toml b/scene_update_macros/Cargo.toml new file mode 100644 index 0000000..c6f9228 --- /dev/null +++ b/scene_update_macros/Cargo.toml @@ -0,0 +1,13 @@ +[package] +name = "scene_update_macros" +version = "0.1.0" +authors = ["hodasemi "] +edition = "2024" + +[lib] +proc-macro = true + +[dependencies] +syn = { workspace = true } +quote = { workspace = true } +proc-macro2 = { workspace = true } diff --git a/scene_update_macros/src/lib.rs b/scene_update_macros/src/lib.rs new file mode 100644 index 0000000..097017f --- /dev/null +++ b/scene_update_macros/src/lib.rs @@ -0,0 +1,117 @@ +use proc_macro::TokenStream; +use proc_macro2::{Span, TokenStream as TokenStream2}; +use quote::{format_ident, quote}; +use syn::{ + parse::{Parse, ParseStream}, + parse_macro_input, + token::Comma, + Ident, LitInt, Result, +}; + +struct InputInfo { + macro_ident: Ident, + start: usize, + end: usize, +} + +impl Parse for InputInfo { + fn parse(input: ParseStream) -> Result { + let macro_ident = input.parse::()?; + input.parse::()?; + let start = input.parse::()?.base10_parse()?; + input.parse::()?; + let end = input.parse::()?.base10_parse()?; + + Ok(Self { + macro_ident, + start, + end, + }) + } +} + +struct TupleType { + little: Ident, + big: Ident, +} + +#[proc_macro] +pub fn implement_pair_update(input: TokenStream) -> TokenStream { + let input = parse_macro_input!(input as InputInfo); + + let mut generic_count = Vec::new(); + + for lhs in input.start..=input.end { + for rhs in input.start..=input.end { + generic_count.push((lhs, rhs)); + } + } + + let generic_tuples: Vec<(Vec, Vec)> = generic_count + .iter() + .map(|(lhs_count, rhs_count)| { + let lhs = (input.start..(input.start + lhs_count)) + .map(|i| TupleType { + little: format_ident!("l{}", i), + big: format_ident!("L{}", i), + }) + .collect(); + + let rhs = (input.start..(input.start + rhs_count)) + .map(|i| TupleType { + little: format_ident!("r{}", i), + big: format_ident!("R{}", i), + }) + .collect(); + + (lhs, rhs) + }) + .collect(); + + let invocations: Vec = generic_tuples + .iter() + .map(|(lhs, rhs)| { + let lhs_expr = LitInt::new(&format!("{}", lhs.len()), Span::call_site()); + let rhs_expr = LitInt::new(&format!("{}", rhs.len()), Span::call_site()); + + let lhs_args: Vec = lhs + .iter() + .map(|tuple| { + let little = &tuple.little; + let big = &tuple.big; + + quote! { + [#little: #big], + } + }) + .collect(); + + let rhs_args: Vec = rhs + .iter() + .map(|tuple| { + let little = &tuple.little; + let big = &tuple.big; + + quote! { + [#little: #big], + } + }) + .collect(); + + let macro_ident = &input.macro_ident; + + quote! { + #macro_ident!( + #lhs_expr, (#(#lhs_args)*), + #rhs_expr, (#(#rhs_args)*) + ); + } + }) + .collect(); + + TokenStream::from(quote! { + #( + #invocations + )* + }) +}