From 0136eb0a0b33afbd37361c2fb74e098948811805 Mon Sep 17 00:00:00 2001
From: hodasemi <michaelh.95@t-online.de>
Date: Tue, 8 Apr 2025 08:14:09 +0200
Subject: [PATCH] Start promoting safe usage

---
 ecs/src/commands.rs                | 68 +++++++++++++++++++++
 ecs/src/events.rs                  |  7 +++
 ecs/src/lib.rs                     |  2 +
 ecs/src/updates.rs                 | 95 +++++++++++++-----------------
 ecs/src/world.rs                   | 13 +++-
 update_macros/src/lib.rs           | 88 ++++-----------------------
 update_macros/src/pair_update.rs   | 85 ++++++++++++++++++++++++++
 update_macros/src/single_update.rs | 33 +++++++++++
 8 files changed, 262 insertions(+), 129 deletions(-)
 create mode 100644 ecs/src/commands.rs
 create mode 100644 update_macros/src/pair_update.rs
 create mode 100644 update_macros/src/single_update.rs

diff --git a/ecs/src/commands.rs b/ecs/src/commands.rs
new file mode 100644
index 0000000..854c63a
--- /dev/null
+++ b/ecs/src/commands.rs
@@ -0,0 +1,68 @@
+use anyhow::Result;
+use std::any::{Any, TypeId};
+
+use crate::{world::ComponentChange, *};
+
+enum CommandsTypes {
+    InsertEntity(EntityObject),
+    RemoveEntity(Entity),
+    UpdateEntity(Entity, ComponentChange),
+
+    Event(TypeId, Box<dyn Any + Send + Sync>),
+}
+
+#[derive(Default)]
+pub struct Commands {
+    commands: Vec<CommandsTypes>,
+}
+
+impl Commands {
+    pub fn insert_entity(&mut self, entity_object: EntityObject) -> Entity {
+        let entity = entity_object.as_entity();
+
+        self.commands
+            .push(CommandsTypes::InsertEntity(entity_object));
+
+        entity
+    }
+
+    pub fn remove_entity(&mut self, entity: Entity) {
+        self.commands.push(CommandsTypes::RemoveEntity(entity));
+    }
+
+    pub fn insert_component<T: EntityComponent>(&mut self, entity: Entity, component: T) {
+        self.commands.push(CommandsTypes::UpdateEntity(
+            entity,
+            ComponentChange::Added(TypeId::of::<T>(), Box::new(component)),
+        ));
+    }
+
+    pub fn remove_component<T: EntityComponent>(&mut self, entity: Entity) {
+        self.commands.push(CommandsTypes::UpdateEntity(
+            entity,
+            ComponentChange::Removed(TypeId::of::<T>()),
+        ));
+    }
+
+    pub fn write_event<T: Any + Send + Sync>(&mut self, payload: T) {
+        self.commands
+            .push(CommandsTypes::Event(TypeId::of::<T>(), Box::new(payload)));
+    }
+
+    pub(crate) fn apply_deferred(self, world: &mut World) -> Result<()> {
+        for command in self.commands {
+            match command {
+                CommandsTypes::InsertEntity(entity_object) => {
+                    world.add_entity(entity_object)?;
+                }
+                CommandsTypes::RemoveEntity(entity) => world.remove_entity(entity),
+                CommandsTypes::UpdateEntity(entity, component_change) => {
+                    world.component_change(entity, component_change)
+                }
+                CommandsTypes::Event(type_id, any) => world.events.write_payload(type_id, any),
+            }
+        }
+
+        Ok(())
+    }
+}
diff --git a/ecs/src/events.rs b/ecs/src/events.rs
index 71abc73..4580c2e 100644
--- a/ecs/src/events.rs
+++ b/ecs/src/events.rs
@@ -72,6 +72,13 @@ impl Events {
         }
     }
 
+    pub(crate) fn write_payload(&mut self, type_id: TypeId, payload: Box<dyn Any + Send + Sync>) {
+        match self.events.get_mut(&type_id) {
+            Some((payloads, _)) => payloads.push(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() {
diff --git a/ecs/src/lib.rs b/ecs/src/lib.rs
index f916a02..b2ec2b1 100644
--- a/ecs/src/lib.rs
+++ b/ecs/src/lib.rs
@@ -1,3 +1,4 @@
+mod commands;
 mod entity;
 mod entity_object_manager;
 mod events;
@@ -17,5 +18,6 @@ pub use crate::type_map::{
 pub use crate::unsafe_component_store::UnsafeComponentStore;
 pub use crate::updates::*;
 pub use crate::world::{World, WorldBuilder};
+pub use commands::Commands;
 pub use get_disjoint_mut::GetDisjointMut;
 pub use update_macros::Resource;
diff --git a/ecs/src/updates.rs b/ecs/src/updates.rs
index f3d52f3..3a8ba38 100644
--- a/ecs/src/updates.rs
+++ b/ecs/src/updates.rs
@@ -14,20 +14,20 @@ use indexmap::IndexMap;
 use super::super::timings::Timings;
 
 use crate::*;
-use update_macros::implement_pair_update;
+use update_macros::{implement_pair_update, implement_single_update};
 
 macro_rules! impl_singleton_update {
-    ( $name: ident, $([$var: ident]$(,)?)+ ) => {
-        impl Archetype {
+    ( $($var:ident $(,)?)+ ) => {
+        impl<Func, Filter, $( $var, )+> CreateArchetype<( $( $var, )+ ), Func, Filter> for Archetype
+        where
+            Func: Fn(&mut Commands, Entity, $(&mut $var,)+) -> Result<()> + Send + Sync + Clone + 'static,
+            Filter: CheckFilter + 'static,
+            $(
+                $var: EntityComponent + ComponentDebug,
+            )+
+        {
             paste::item! {
-                pub fn [<create_ $name>]<F, Filter, $($var,)+>(f: F, filter: Filter) -> Self
-                where
-                    F: Fn(&mut World, Entity, $(&mut $var,)+) -> Result<()> + Send + Sync + Clone + 'static,
-                    Filter: CheckFilter + 'static,
-                    $(
-                        $var: EntityComponent + ComponentDebug,
-                    )+
-                {
+                fn create(f: Func, filter: Filter) -> Self {
                     $(
                         filter.verify_dedup::<$var>();
                     )+
@@ -58,8 +58,8 @@ macro_rules! impl_singleton_update {
 
                             let f = f.clone();
 
-                            Ok(Box::new(move |e, scene_contents| {
-                                unsafe { f(scene_contents, e, $([< $var:lower >].as_mut(),)+) }
+                            Ok(Box::new(move |e, commands| {
+                                unsafe { f(commands, e, $([< $var:lower >].as_mut(),)+) }
                             }))
                         }),
 
@@ -69,12 +69,12 @@ macro_rules! impl_singleton_update {
             }
         }
 
-        impl<Func, Filter, $( $var, )+> AddUpdates2<( $( $var, )+ ), Func, Filter> for Updates
+        impl<Func, Filter, $( $var, )+> AddUpdates<( $( $var, )+ ), Func, Filter> for Updates
         where
             $(
                 $var: EntityComponent + ComponentDebug,
             )+
-            Func: Fn(& mut World, Entity, $(&mut $var,)+) -> Result<()> + Send + Sync + Clone + 'static,
+            Func: Fn(& mut Commands, Entity, $(&mut $var,)+) -> Result<()> + Send + Sync + Clone + 'static,
             Filter: CheckFilter + 'static
         {
             fn add_update(
@@ -85,7 +85,7 @@ macro_rules! impl_singleton_update {
                 filter: Filter
             ) -> Result<()> {
                 paste::item! {
-                    self.add(name, priority, Update::Single(Archetype::[<create_ $name>](func, filter)))
+                    self.add(name, priority, Update::Single(Archetype::create(func, filter)))
                 }
             }
         }
@@ -95,7 +95,7 @@ macro_rules! impl_singleton_update {
             $(
                 $var: EntityComponent + ComponentDebug,
             )+
-            Func: Fn(& mut World, Entity, $(&mut $var,)+) -> Result<()> + Send + Sync + Clone + 'static,
+            Func: Fn(& mut Commands, Entity, $(&mut $var,)+) -> Result<()> + Send + Sync + Clone + 'static,
             Filter: CheckFilter + 'static
         {
             fn add_update(
@@ -123,7 +123,7 @@ macro_rules! impl_pair_update {
                 pub fn [<create_lhs_ $lhs_id _rhs_ $rhs_id>] <F, LeftFilter, RightFilter, $($lhs_big,)+ $($rhs_big,)+>
                     (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,
+                    F: Fn(&mut Commands, (Entity, $(&mut $lhs_big,)+), (Entity, $(&mut $rhs_big,)+)) -> Result<()> + Send + Sync + Clone + 'static,
                     LeftFilter: CheckFilter + 'static,
                     RightFilter: CheckFilter + 'static,
                     $(
@@ -188,8 +188,8 @@ macro_rules! impl_pair_update {
 
                             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(),)+) ) }
+                            Ok(Box::new(move |lhs_e, rhs_e, commands| {
+                                unsafe { f(commands, (lhs_e, $($lhs_little.as_mut(),)+), (rhs_e, $($rhs_little.as_mut(),)+) ) }
                             }))
                         }),
 
@@ -199,7 +199,7 @@ macro_rules! impl_pair_update {
             }
         }
 
-        impl<Func, LhsFilter, RhsFilter, $( $lhs_big, )+ $($rhs_big,)+> AddUpdates2<( ($( $lhs_big, )+), ($($rhs_big,)+) ), Func, (LhsFilter, RhsFilter)> for Updates
+        impl<Func, LhsFilter, RhsFilter, $( $lhs_big, )+ $($rhs_big,)+> AddUpdates<( ($( $lhs_big, )+), ($($rhs_big,)+) ), Func, (LhsFilter, RhsFilter)> for Updates
         where
             $(
                 $rhs_big: EntityComponent + ComponentDebug,
@@ -207,7 +207,7 @@ macro_rules! impl_pair_update {
             $(
                 $lhs_big: EntityComponent + ComponentDebug,
             )+
-            Func: Fn(& mut World, (Entity, $(&mut $lhs_big,)+), (Entity, $(&mut $rhs_big,)+)) -> Result<()> + Send + Sync + Clone + 'static,
+            Func: Fn(&mut Commands, (Entity, $(&mut $lhs_big,)+), (Entity, $(&mut $rhs_big,)+)) -> Result<()> + Send + Sync + Clone + 'static,
             LhsFilter: CheckFilter + 'static,
             RhsFilter: CheckFilter + 'static
         {
@@ -234,7 +234,7 @@ macro_rules! impl_pair_update {
             $(
                 $lhs_big: EntityComponent + ComponentDebug,
             )+
-            Func: Fn(& mut World, (Entity, $(&mut $lhs_big,)+), (Entity, $(&mut $rhs_big,)+)) -> Result<()> + Send + Sync + Clone + 'static,
+            Func: Fn(& mut Commands, (Entity, $(&mut $lhs_big,)+), (Entity, $(&mut $rhs_big,)+)) -> Result<()> + Send + Sync + Clone + 'static,
             LhsFilter: CheckFilter + 'static,
             RhsFilter: CheckFilter + 'static
         {
@@ -316,8 +316,8 @@ pub trait AddUpdates<T, Func, Filter> {
     fn add_update(&mut self, name: &str, priority: u32, func: Func, filter: Filter) -> Result<()>;
 }
 
-trait AddUpdates2<T, Func, Filter> {
-    fn add_update(&mut self, name: &str, priority: u32, func: Func, filter: Filter) -> Result<()>;
+trait CreateArchetype<T, Func, Filter> {
+    fn create(f: Func, filter: Filter) -> Self;
 }
 
 pub trait CheckFilter: Send + Sync + Default + Clone {
@@ -358,12 +358,15 @@ impl ArchetypeInfo {
 pub struct Archetype {
     check_entity: Box<dyn Fn(&EntityObject) -> bool + Send + Sync>,
     create_callback: Box<
-        dyn Fn(&EntityObject) -> Result<Box<dyn Fn(Entity, &mut World) -> Result<()> + Send + Sync>>
+        dyn Fn(
+                &EntityObject,
+            )
+                -> Result<Box<dyn Fn(Entity, &mut Commands) -> Result<()> + Send + Sync>>
             + Send
             + Sync,
     >,
 
-    entities: IndexMap<Entity, Box<dyn Fn(Entity, &mut World) -> Result<()> + Send + Sync>>,
+    entities: IndexMap<Entity, Box<dyn Fn(Entity, &mut Commands) -> Result<()> + Send + Sync>>,
 }
 
 impl Archetype {
@@ -381,9 +384,11 @@ impl Archetype {
         self.entities.swap_remove(&entity);
     }
 
-    pub fn execute(&self, scene_contents: &mut World) -> Result<()> {
+    pub fn execute(&self, world: &mut World) -> Result<()> {
         for (entity, callback) in self.entities.iter() {
-            callback(*entity, scene_contents)?;
+            let mut commands = Commands::default();
+            callback(*entity, &mut commands)?;
+            commands.apply_deferred(world)?;
         }
 
         Ok(())
@@ -391,7 +396,7 @@ impl Archetype {
 
     pub fn entities(
         &self,
-    ) -> &IndexMap<Entity, Box<dyn Fn(Entity, &mut World) -> Result<()> + Send + Sync>> {
+    ) -> &IndexMap<Entity, Box<dyn Fn(Entity, &mut Commands) -> Result<()> + Send + Sync>> {
         &self.entities
     }
 }
@@ -405,14 +410,14 @@ pub struct ArchetypePair {
                 &EntityObject,
                 &EntityObject,
             )
-                -> Result<Box<dyn Fn(Entity, Entity, &mut World) -> Result<()> + Send + Sync>>
+                -> Result<Box<dyn Fn(Entity, Entity, &mut Commands) -> Result<()> + Send + Sync>>
             + Send
             + Sync,
     >,
 
     entities: IndexMap<
         (Entity, Entity),
-        Box<dyn Fn(Entity, Entity, &mut World) -> Result<()> + Send + Sync>,
+        Box<dyn Fn(Entity, Entity, &mut Commands) -> Result<()> + Send + Sync>,
     >,
 }
 
@@ -461,9 +466,11 @@ impl ArchetypePair {
         }
     }
 
-    pub(crate) fn execute(&self, scene_contents: &mut World) -> Result<()> {
+    pub(crate) fn execute(&self, world: &mut World) -> Result<()> {
         for ((left_entity, right_entity), callback) in self.entities.iter() {
-            callback(*left_entity, *right_entity, scene_contents)?;
+            let mut commands = Commands::default();
+            callback(*left_entity, *right_entity, &mut commands)?;
+            commands.apply_deferred(world)?;
         }
 
         Ok(())
@@ -625,26 +632,8 @@ impl Archetypes {
     }
 }
 
-#[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);
+implement_single_update!(impl_singleton_update, 1, 10);
 
 #[rustfmt::skip]
 impl_update_filter!(Monuple,    [R, r]);
diff --git a/ecs/src/world.rs b/ecs/src/world.rs
index 3d0ec8f..b1e4926 100644
--- a/ecs/src/world.rs
+++ b/ecs/src/world.rs
@@ -58,7 +58,7 @@ impl WorldBuilder {
     }
 }
 
-enum ComponentChange {
+pub(crate) enum ComponentChange {
     Added(TypeId, Box<dyn EntityComponent>),
     Removed(TypeId),
 }
@@ -249,6 +249,17 @@ impl World {
         Ok(())
     }
 
+    pub(crate) fn component_change(&mut self, entity: Entity, change: ComponentChange) {
+        match self.entities_updates.get_mut(&entity) {
+            Some(changes) => {
+                changes.push(change);
+            }
+            None => {
+                self.entities_updates.insert(entity, vec![change]);
+            }
+        }
+    }
+
     pub fn remove_entity(&mut self, entity: Entity) {
         self.entities_to_remove.push(entity);
     }
diff --git a/update_macros/src/lib.rs b/update_macros/src/lib.rs
index 12f4f60..9a7c524 100644
--- a/update_macros/src/lib.rs
+++ b/update_macros/src/lib.rs
@@ -1,6 +1,11 @@
+mod pair_update;
+mod single_update;
+
+use pair_update::pair_update;
+use single_update::single_update;
+
 use proc_macro::TokenStream;
-use proc_macro2::{Span, TokenStream as TokenStream2};
-use quote::{format_ident, quote};
+use quote::quote;
 use syn::{
     DeriveInput, Ident, LitInt, Result,
     parse::{Parse, ParseStream},
@@ -39,81 +44,14 @@ struct TupleType {
 pub fn implement_pair_update(input: TokenStream) -> TokenStream {
     let input = parse_macro_input!(input as InputInfo);
 
-    let mut generic_count = Vec::new();
+    pair_update(input)
+}
 
-    for lhs in input.start..=input.end {
-        for rhs in input.start..=input.end {
-            generic_count.push((lhs, rhs));
-        }
-    }
+#[proc_macro]
+pub fn implement_single_update(input: TokenStream) -> TokenStream {
+    let input = parse_macro_input!(input as InputInfo);
 
-    let generic_tuples: Vec<(Vec<TupleType>, Vec<TupleType>)> = 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<TokenStream2> = 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<TokenStream2> = lhs
-                .iter()
-                .map(|tuple| {
-                    let little = &tuple.little;
-                    let big = &tuple.big;
-
-                    quote! {
-                        [#little: #big],
-                    }
-                })
-                .collect();
-
-            let rhs_args: Vec<TokenStream2> = 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
-        )*
-    })
+    single_update(input)
 }
 
 #[proc_macro_derive(Resource)]
diff --git a/update_macros/src/pair_update.rs b/update_macros/src/pair_update.rs
new file mode 100644
index 0000000..f9c90db
--- /dev/null
+++ b/update_macros/src/pair_update.rs
@@ -0,0 +1,85 @@
+use proc_macro::TokenStream;
+use proc_macro2::{Span, TokenStream as TokenStream2};
+use quote::{format_ident, quote};
+use syn::LitInt;
+
+use crate::InputInfo;
+use crate::TupleType;
+
+pub fn pair_update(input: InputInfo) -> TokenStream {
+    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<TupleType>, Vec<TupleType>)> = 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<TokenStream2> = 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<TokenStream2> = lhs
+                .iter()
+                .map(|tuple| {
+                    let little = &tuple.little;
+                    let big = &tuple.big;
+
+                    quote! {
+                        [#little: #big],
+                    }
+                })
+                .collect();
+
+            let rhs_args: Vec<TokenStream2> = 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
+        )*
+    })
+}
diff --git a/update_macros/src/single_update.rs b/update_macros/src/single_update.rs
new file mode 100644
index 0000000..6441606
--- /dev/null
+++ b/update_macros/src/single_update.rs
@@ -0,0 +1,33 @@
+use proc_macro::TokenStream;
+use proc_macro2::TokenStream as TokenStream2;
+use quote::{format_ident, quote};
+use syn::Ident;
+
+use crate::InputInfo;
+
+pub fn single_update(input: InputInfo) -> TokenStream {
+    let generate_inputs: Vec<Vec<Ident>> = (input.start..=input.end)
+        .map(|count| {
+            (input.start..(input.start + count))
+                .map(|i| format_ident!("t{}", i))
+                .collect()
+        })
+        .collect();
+
+    let invocations: Vec<TokenStream2> = generate_inputs
+        .iter()
+        .map(|t| {
+            let macro_ident = &input.macro_ident;
+
+            quote! {
+                #macro_ident!(#(#t,)*);
+            }
+        })
+        .collect();
+
+    TokenStream::from(quote! {
+        #(
+            #invocations
+        )*
+    })
+}