summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorHampusM <hampus@hampusmat.com>2026-06-20 16:01:00 +0200
committerHampusM <hampus@hampusmat.com>2026-06-23 00:25:26 +0200
commit7b3374ad9585f78c60e1b158126ab54384a83f32 (patch)
treeee14cd0bebd6554fab43685da89c459310f0e97d
parentd6cf708a4fd1caf0f2e193ceb7c23fa8e77cc1db (diff)
refactor(engine-ecs): store soles as components
-rw-r--r--engine-ecs-macros/src/lib.rs175
-rw-r--r--engine-ecs/src/component.rs17
-rw-r--r--engine-ecs/src/lib.rs87
-rw-r--r--engine-ecs/src/sole.rs57
4 files changed, 142 insertions, 194 deletions
diff --git a/engine-ecs-macros/src/lib.rs b/engine-ecs-macros/src/lib.rs
index a304706..106d350 100644
--- a/engine-ecs-macros/src/lib.rs
+++ b/engine-ecs-macros/src/lib.rs
@@ -6,7 +6,6 @@ use quote::{format_ident, quote, ToTokens};
use syn::spanned::Spanned;
use syn::{
parse,
- Attribute,
Generics,
Ident,
Item,
@@ -148,6 +147,17 @@ pub fn component_derive(input: TokenStream) -> TokenStream
#where_clause
{
}
+
+ impl #ecs_path::component::IntoParts for #item_ident
+ {
+ fn into_parts(self) -> #ecs_path::component::Parts
+ {
+ #ecs_path::component::Parts::builder()
+ .name(self.name())
+ .type_reflection(<Self as Component>::type_reflection())
+ .build(Self::id(), self)
+ }
+ }
}
}
.into()
@@ -156,50 +166,118 @@ pub fn component_derive(input: TokenStream) -> TokenStream
/// Generates a `Sole` implementation.
///
/// # Panics
-/// Will panic if not attributed to a type item or if parsing the user crate's
-/// `Cargo.toml` file fails.
-#[proc_macro_derive(Sole, attributes(sole))]
+/// Will panic if:
+/// - Not attributed to a type item
+/// - The attributed-to type item is generic
+/// - If parsing the user crate's `Cargo.toml` file fails.
+#[proc_macro_derive(Sole)]
pub fn sole_derive(input: TokenStream) -> TokenStream
{
let item: TypeItem = parse::<Item>(input).unwrap().try_into().unwrap();
let item_ident = item.ident();
- let sole_attr = item.attribute::<SoleAttribute>("sole").unwrap_or_default();
+ let ecs_path = find_engine_ecs_crate_path().unwrap_or_else(|| syn_path!(ecs));
- let drop_last = sole_attr.drop_last;
+ assert!(
+ item.generics().params.is_empty(),
+ "Generic types are not supported as sole components"
+ );
- let (impl_generics, type_generics, where_clause) = item.generics().split_for_impl();
+ let id_var_ident = format_ident!("{}_ID", item_ident.to_string().to_uppercase());
- let ecs_path = find_engine_ecs_crate_path().unwrap_or_else(|| syn_path!(ecs));
+ let id_var = quote! {
+ static #id_var_ident: LazyLock<Uid> = LazyLock::new(|| {
+ Uid::new_unique()
+ });
+ };
+
+ let mod_ident = format_ident!(
+ "__ecs_priv_sole_impl_{}",
+ item_ident.to_string().to_lowercase()
+ );
quote! {
- impl #impl_generics #ecs_path::sole::Sole for #item_ident #type_generics
- #where_clause
- {
- fn drop_last(&self) -> bool
- {
- #drop_last
- }
+ mod #mod_ident {
+ use ::std::any::{Any, TypeId};
+ use ::std::sync::{LazyLock, Mutex};
+
+ use #ecs_path::sole::Sole;
+ use #ecs_path::uid::Uid;
+ use #ecs_path::system::Input as SystemInput;
+
+ use super::*;
+
+ #id_var
- fn as_any_mut(&mut self) -> &mut dyn std::any::Any
+ impl Sole for #item_ident
{
- self
+ fn id() -> Uid
+ {
+ *#id_var_ident
+ }
+
+ fn type_reflection() -> Option<&'static #ecs_path::reflection::Type>
+ {
+ struct SpecializationTarget<T>(std::marker::PhantomData<T>);
+
+ 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<T> NoReflection for &SpecializationTarget<T>
+ {
+ fn type_reflection(&self)
+ -> Option<&'static #ecs_path::reflection::Type>
+ {
+ None
+ }
+ }
+
+ impl<T> HasReflection for SpecializationTarget<T>
+ where
+ T: #ecs_path::reflection::Reflection
+ {
+ fn type_reflection(&self)
+ -> Option<&'static #ecs_path::reflection::Type>
+ {
+ Some(T::type_reflection())
+ }
+ }
+
+ (&SpecializationTarget::<Self>(std::marker::PhantomData))
+ .type_reflection()
+ }
+
+ fn name(&self) -> &'static str
+ {
+ std::any::type_name::<Self>()
+ }
}
- fn as_any(&self) -> &dyn std::any::Any
+ impl #ecs_path::component::IntoParts for #item_ident
{
- self
+ fn into_parts(self) -> #ecs_path::component::Parts
+ {
+ #ecs_path::component::Parts::builder()
+ .name(self.name())
+ .type_reflection(<Self as Sole>::type_reflection())
+ .build(Self::id(), self)
+ }
}
}
}
.into()
}
-trait FromAttribute: Sized
-{
- fn from_attribute(attribute: &Attribute) -> Result<Self, syn::Error>;
-}
enum TypeItem
{
@@ -219,27 +297,6 @@ impl TypeItem
}
}
- fn attribute<Attr: FromAttribute>(&self, attr_ident: &str) -> Option<Attr>
- {
- let item_attrs = match &self {
- Self::Struct(struct_item) => &struct_item.attrs,
- &Self::Enum(enum_item) => &enum_item.attrs,
- &Self::Union(union_item) => &union_item.attrs,
- };
-
- let mut attr: Option<&Attribute> = None;
-
- for item_attr in item_attrs {
- assert!(attr.is_none(), "Expected only one {attr_ident} attribute");
-
- if item_attr.path().get_ident()? == attr_ident {
- attr = Some(item_attr);
- }
- }
-
- Some(Attr::from_attribute(attr?).unwrap())
- }
-
fn generics(&self) -> &Generics
{
match self {
@@ -280,36 +337,6 @@ impl ToTokens for TypeItem
}
}
-#[derive(Debug, Default)]
-struct SoleAttribute
-{
- drop_last: bool,
-}
-
-impl FromAttribute for SoleAttribute
-{
- fn from_attribute(attribute: &Attribute) -> Result<Self, syn::Error>
- {
- let mut drop_last = false;
-
- attribute.parse_nested_meta(|meta| {
- if meta
- .path
- .get_ident()
- .is_some_and(|flag| flag == "drop_last")
- {
- drop_last = true;
-
- return Ok(());
- }
-
- Err(meta.error("Unrecognized token"))
- })?;
-
- Ok(Self { drop_last })
- }
-}
-
fn find_engine_ecs_crate_path() -> Option<Path>
{
let cargo_manifest_dir = FsPathBuf::from(std::env::var("CARGO_MANIFEST_DIR").ok()?);
diff --git a/engine-ecs/src/component.rs b/engine-ecs/src/component.rs
index 157d79c..25ec101 100644
--- a/engine-ecs/src/component.rs
+++ b/engine-ecs/src/component.rs
@@ -1,4 +1,4 @@
-use std::any::{type_name, Any};
+use std::any::Any;
use std::fmt::Debug;
use std::ops::{Deref, DerefMut};
@@ -24,7 +24,7 @@ pub mod local;
pub(crate) mod storage;
-pub trait Component: SystemInput + Any
+pub trait Component: SystemInput + Any + IntoParts
{
/// Returns the ID of this component.
fn id() -> Uid
@@ -286,19 +286,6 @@ pub trait IntoParts
fn into_parts(self) -> Parts;
}
-impl<ComponentT> IntoParts for ComponentT
-where
- ComponentT: Component,
-{
- fn into_parts(self) -> Parts
- {
- Parts::builder()
- .name(type_name::<Self>())
- .type_reflection(Self::type_reflection())
- .build(Self::id(), self)
- }
-}
-
/// The parts of a component.
#[derive(Debug)]
#[non_exhaustive]
diff --git a/engine-ecs/src/lib.rs b/engine-ecs/src/lib.rs
index 1c157e3..25866ca 100644
--- a/engine-ecs/src/lib.rs
+++ b/engine-ecs/src/lib.rs
@@ -1,14 +1,10 @@
#![deny(clippy::all, clippy::pedantic)]
-use std::any::{type_name, Any, TypeId};
+use std::any::Any;
use std::fmt::Debug;
use std::hint::cold_path;
-use std::mem::ManuallyDrop;
use std::rc::Rc;
use std::sync::atomic::{AtomicBool, Ordering};
-use std::sync::Arc;
-
-use hashbrown::HashMap;
use crate::actions::Action;
use crate::component::storage::archetype::EntityComponent as ArchetypeEntityComponent;
@@ -156,7 +152,7 @@ impl World
entity_decl.create(self);
}
- /// Adds a globally shared singleton value.
+ /// Adds a singleton.
///
/// # Errors
/// Returns `Err` if this [`Sole`] has already been added.
@@ -164,7 +160,9 @@ impl World
where
SoleT: Sole,
{
- self.data.sole_storage.insert(sole)
+ self.create_ent(SoleT::id(), [sole.into_parts()]);
+
+ Ok(())
}
pub fn register_observer<'this, SystemImpl, ObserverT>(
@@ -253,7 +251,9 @@ impl World
pub fn get_sole<SoleT: Sole>(&self) -> Option<Single<'_, SoleT>>
{
- Some(Single::new(self.data.sole_storage.get::<SoleT>()?))
+ let ent = self.get_entity(SoleT::id())?;
+
+ Some(Single::new(ent.get_with_id_mut(SoleT::id())?))
}
pub fn event_submitter(&self) -> EventSubmitter<'_>
@@ -701,7 +701,6 @@ pub enum StepResult
struct WorldData
{
component_storage: ComponentStorage,
- sole_storage: SoleStorage,
action_queue: Rc<ActionQueue>,
new_events: Lock<NewEvents>,
}
@@ -765,73 +764,3 @@ impl ActionQueue
#[error("Sole {0} already exists")]
pub struct SoleAlreadyExistsError(pub &'static str);
-#[derive(Debug)]
-struct StoredSole
-{
- sole: Arc<Lock<Box<dyn Sole>>>,
- drop_last: bool,
-}
-
-#[derive(Debug, Default)]
-struct SoleStorage
-{
- storage: HashMap<TypeId, ManuallyDrop<StoredSole>>,
-}
-
-impl SoleStorage
-{
- fn get<SoleT: Sole>(&self) -> Option<&Arc<Lock<Box<dyn Sole>>>>
- {
- self.storage
- .get(&TypeId::of::<SoleT>())
- .map(|sole| &sole.sole)
- }
-
- fn insert<SoleT: Sole>(&mut self, sole: SoleT) -> Result<(), SoleAlreadyExistsError>
- {
- let sole_type_id = TypeId::of::<SoleT>();
-
- if self.storage.contains_key(&sole_type_id) {
- return Err(SoleAlreadyExistsError(type_name::<SoleT>()));
- }
-
- let drop_last = sole.drop_last();
-
- // TODO: Reconsider this maybe?
- #[allow(clippy::arc_with_non_send_sync)]
- self.storage.insert(
- sole_type_id,
- ManuallyDrop::new(StoredSole {
- sole: Arc::new(Lock::new(Box::new(sole), type_name::<SoleT>())),
- drop_last,
- }),
- );
-
- Ok(())
- }
-}
-
-impl Drop for SoleStorage
-{
- fn drop(&mut self)
- {
- let mut soles_to_drop_last = Vec::new();
-
- for sole in self.storage.values_mut() {
- if sole.drop_last {
- soles_to_drop_last.push(sole);
- continue;
- }
-
- unsafe {
- ManuallyDrop::drop(sole);
- }
- }
-
- for sole in &mut soles_to_drop_last {
- unsafe {
- ManuallyDrop::drop(sole);
- }
- }
- }
-}
diff --git a/engine-ecs/src/sole.rs b/engine-ecs/src/sole.rs
index 9e27fee..9409bcb 100644
--- a/engine-ecs/src/sole.rs
+++ b/engine-ecs/src/sole.rs
@@ -1,33 +1,43 @@
use std::any::{type_name, Any};
use std::fmt::Debug;
-use std::marker::PhantomData;
use std::ops::{Deref, DerefMut};
-use std::sync::Arc;
-use crate::lock::{Lock, WriteGuard};
+use crate::uid::Uid;
+use crate::component::HandleMut as ComponentHandleMut;
+use crate::component::IntoParts as IntoComponentParts;
use crate::system::{Metadata as SystemMetadata, Param as SystemParam};
use crate::World;
/// A type which has a single instance and is shared globally.
-pub trait Sole: Any
+pub trait Sole: Any + IntoComponentParts
{
- fn drop_last(&self) -> bool;
+ fn id() -> Uid
+ where
+ Self: Sized;
- fn as_any_mut(&mut self) -> &mut dyn Any;
+ fn type_reflection() -> Option<&'static crate::reflection::Type>
+ where
+ Self: Sized;
- fn as_any(&self) -> &dyn Any;
+ /// Returns the name of this component.
+ fn name(&self) -> &'static str;
}
impl dyn Sole
{
pub fn downcast_mut<Real: 'static>(&mut self) -> Option<&mut Real>
{
- self.as_any_mut().downcast_mut()
+ (self as &mut dyn Any).downcast_mut()
}
pub fn downcast_ref<Real: 'static>(&self) -> Option<&Real>
{
- self.as_any().downcast_ref()
+ (self as &dyn Any).downcast_ref()
+ }
+
+ pub fn is<Other: 'static>(&self) -> bool
+ {
+ (self as &dyn Any).is::<Other>()
}
}
@@ -43,25 +53,17 @@ impl Debug for dyn Sole
#[derive(Debug)]
pub struct Single<'world, SoleT: Sole>
{
- sole: WriteGuard<'world, Box<dyn Sole>>,
- _ph: PhantomData<SoleT>,
+ sole: ComponentHandleMut<'world, SoleT>,
}
+
impl<'world, SoleT> Single<'world, SoleT>
where
SoleT: Sole,
{
- pub(crate) fn new(sole: &'world Arc<Lock<Box<dyn Sole>>>) -> Self
+ pub(crate) fn new(sole: ComponentHandleMut<'world, SoleT>) -> Self
{
- Self {
- sole: sole.write_nonblock().unwrap_or_else(|_| {
- panic!(
- "Failed to aquire read-write lock to single component {}",
- type_name::<SoleT>()
- )
- }),
- _ph: PhantomData,
- }
+ Self { sole }
}
}
@@ -73,9 +75,12 @@ where
fn new(world: &'world World, _system_metadata: &SystemMetadata) -> Self
{
- let sole = world.data.sole_storage.get::<SoleT>().unwrap_or_else(|| {
- panic!("Sole {} was not found in world", type_name::<SoleT>())
- });
+ let sole = world
+ .get_entity(SoleT::id())
+ .and_then(|ent| ent.get_with_id_mut::<SoleT>(SoleT::id()))
+ .unwrap_or_else(|| {
+ panic!("Sole component {} was not found in world", type_name::<SoleT>())
+ });
Self::new(sole)
}
@@ -89,7 +94,7 @@ where
fn deref(&self) -> &Self::Target
{
- self.sole.downcast_ref().unwrap()
+ &*self.sole
}
}
@@ -99,6 +104,6 @@ where
{
fn deref_mut(&mut self) -> &mut Self::Target
{
- self.sole.downcast_mut().unwrap()
+ &mut *self.sole
}
}