From 3f099cddf16f2e7e002cc24217ed3cc5da422156 Mon Sep 17 00:00:00 2001 From: HampusM Date: Sun, 7 Jun 2026 00:41:15 +0200 Subject: feat(engine-ecs): add component reflection support --- Cargo.lock | 1 + engine-ecs-macros/src/lib.rs | 40 +++++++++++++++++++++++++++++ engine-ecs/Cargo.toml | 3 ++- engine-ecs/src/component.rs | 60 ++++++++++++++++++++++++++++---------------- engine-ecs/src/lib.rs | 52 +++++++++++++++++++++++++++++++++++--- 5 files changed, 129 insertions(+), 27 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index f0a0129..6a70f80 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -672,6 +672,7 @@ dependencies = [ "backtrace", "criterion", "engine-ecs-macros", + "engine-reflection", "hashbrown 0.15.2", "parking_lot", "paste", diff --git a/engine-ecs-macros/src/lib.rs b/engine-ecs-macros/src/lib.rs index 3eb02dd..a304706 100644 --- a/engine-ecs-macros/src/lib.rs +++ b/engine-ecs-macros/src/lib.rs @@ -98,6 +98,46 @@ pub fn component_derive(input: TokenStream) -> TokenStream *#id_var_ident } + fn type_reflection() -> Option<&'static #ecs_path::reflection::Type> + { + struct SpecializationTarget(std::marker::PhantomData); + + trait HasReflection + { + fn type_reflection(&self) + -> Option<&'static #ecs_path::reflection::Type>; + } + + trait NoReflection + { + fn type_reflection(&self) + -> Option<&'static #ecs_path::reflection::Type>; + } + + impl NoReflection for &SpecializationTarget + { + fn type_reflection(&self) + -> Option<&'static #ecs_path::reflection::Type> + { + None + } + } + + impl HasReflection for SpecializationTarget + where + T: #ecs_path::reflection::Reflection + { + fn type_reflection(&self) + -> Option<&'static #ecs_path::reflection::Type> + { + Some(T::type_reflection()) + } + } + + (&SpecializationTarget::(std::marker::PhantomData)) + .type_reflection() + } + fn name(&self) -> &'static str { std::any::type_name::() diff --git a/engine-ecs/Cargo.toml b/engine-ecs/Cargo.toml index 724aaea..0c9da60 100644 --- a/engine-ecs/Cargo.toml +++ b/engine-ecs/Cargo.toml @@ -15,7 +15,8 @@ hashbrown = "0.15.2" parking_lot = "0.12.3" anyhow = "1.0.102" backtrace = "0.3.76" -engine-ecs-macros = { workspace = true } +engine-ecs-macros = { workspace = true } +engine-reflection = { workspace = true } util-macros = { workspace = true } vizoxide = { version = "1.0.5", optional = true } diff --git a/engine-ecs/src/component.rs b/engine-ecs/src/component.rs index 17b279b..f881128 100644 --- a/engine-ecs/src/component.rs +++ b/engine-ecs/src/component.rs @@ -2,6 +2,7 @@ use std::any::{type_name, Any}; use std::fmt::Debug; use std::ops::{Deref, DerefMut}; +use engine_ecs_macros::Component; use seq_macro::seq; use crate::event::component::Changed; @@ -30,6 +31,10 @@ pub trait Component: SystemInput + Any where Self: Sized; + fn type_reflection() -> Option<&'static crate::reflection::Type> + where + Self: Sized; + /// Returns the name of this component. fn name(&self) -> &'static str; } @@ -243,6 +248,7 @@ where { Parts::builder() .name(type_name::()) + .type_reflection(Self::type_reflection()) .build(Self::id(), self) } } @@ -252,41 +258,26 @@ where #[non_exhaustive] pub struct Parts { - id: Uid, - name: &'static str, - data: Box, + pub id: Uid, + pub name: &'static str, + pub type_reflection: Option<&'static crate::reflection::Type>, + pub data: Box, } impl Parts { - #[must_use] - pub fn id(&self) -> Uid - { - self.id - } - - #[must_use] - pub fn name(&self) -> &'static str - { - self.name - } - #[must_use] pub fn builder() -> PartsBuilder { PartsBuilder::default() } - - pub(crate) fn into_data(self) -> Box - { - self.data - } } #[derive(Debug)] pub struct PartsBuilder { name: &'static str, + type_reflection: Option<&'static crate::reflection::Type>, } impl PartsBuilder @@ -298,12 +289,23 @@ impl PartsBuilder self } + #[must_use] + pub fn type_reflection( + mut self, + type_reflection: Option<&'static crate::reflection::Type>, + ) -> Self + { + self.type_reflection = type_reflection; + self + } + #[must_use] pub fn build(self, id: Uid, data: Data) -> Parts { Parts { id, name: self.name, + type_reflection: self.type_reflection, data: Box::new(data), } } @@ -311,7 +313,12 @@ impl PartsBuilder #[must_use] pub fn build_with_any_data(self, id: Uid, data: Box) -> Parts { - Parts { id, name: self.name, data } + Parts { + id, + name: self.name, + type_reflection: self.type_reflection, + data, + } } } @@ -319,6 +326,15 @@ impl Default for PartsBuilder { fn default() -> Self { - Self { name: "(unspecified)" } + Self { + name: "(unspecified)", + type_reflection: None, + } } } + +#[derive(Debug, Clone, Component)] +pub struct Type +{ + pub type_reflection: &'static crate::reflection::Type, +} diff --git a/engine-ecs/src/lib.rs b/engine-ecs/src/lib.rs index 6880a24..2e30bb8 100644 --- a/engine-ecs/src/lib.rs +++ b/engine-ecs/src/lib.rs @@ -18,6 +18,7 @@ use crate::component::{ IntoParts as IntoComponentParts, Parts as ComponentParts, Sequence as ComponentSequence, + Type as ComponentType, }; use crate::entity::{Declaration as EntityDeclaration, Handle as EntityHandle}; use crate::error::{ @@ -47,6 +48,7 @@ use crate::query::{ TermsBuilderInterface, MAX_TERM_CNT as QUERY_MAX_TERM_CNT, }; +use crate::reflection::Type as TypeReflection; use crate::sole::{Single, Sole}; use crate::stats::Stats; use crate::system::observer::Observer; @@ -81,6 +83,8 @@ pub use engine_ecs_macros::{Component, Sole}; pub use crate::query::Query; +pub extern crate engine_reflection as reflection; + #[derive(Debug)] pub struct World { @@ -546,13 +550,12 @@ impl World let component_iter = components.into_iter(); for component_parts in component_iter { - let comp_id = component_parts.id(); - - let comp_name = component_parts.name(); + let comp_id = component_parts.id; + let comp_name = component_parts.name; if let Err(err) = component_storage.add_entity_component( entity_uid, - (comp_id, comp_name, component_parts.into_data()), + (comp_id, comp_name, component_parts.data), ) { tracing::error!("Failed to add component {comp_name} to entity: {err}"); continue; @@ -562,6 +565,14 @@ impl World continue; } + if let Some(type_reflection) = component_parts.type_reflection { + Self::create_component_type_entity( + comp_id, + type_reflection, + component_storage, + ); + } + event_submitter.submit_event( &Pair::builder() .relation::() @@ -572,6 +583,39 @@ impl World } } + fn create_component_type_entity( + comp_id: Uid, + type_reflection: &'static TypeReflection, + component_storage: &mut ComponentStorage, + ) + { + if let Some(comp_ent_archetype) = component_storage.get_entity_archetype(comp_id) + { + if comp_ent_archetype.contains_component_with_exact_id(ComponentType::id()) { + return; + } + } else { + if let Err(EntityAlreadyExistsError) = + component_storage.create_entity(comp_id) + { + unreachable!(); + } + } + + let ComponentParts { + id: comp_type_id, + name: comp_type_name, + type_reflection: _, + data: comp_type_data, + } = ComponentType { type_reflection }.into_parts(); + + if let Err(err) = component_storage + .add_entity_component(comp_id, (comp_type_id, comp_type_name, comp_type_data)) + { + tracing::error!("Failed to add component {comp_type_name} to entity: {err}"); + } + } + fn remove_entity_components( entity_uid: Uid, component_ids: impl IntoIterator, -- cgit v1.2.3-18-g5258