Ralith / hecs

A handy ECS
Apache License 2.0
924 stars 81 forks source link

Column serialize all serializable components #296

Closed konceptosociala closed 1 year ago

konceptosociala commented 1 year ago

If I want to make save-load implementation for World in my game/engine, I would like to save all components I may, which are a part of a scene being saved. But according to docs I can only serialize a limited range of components, which are hardcoded like

try_serialize::<Position, _>(archetype, &mut out)?;
try_serialize::<Velocity, _>(archetype, &mut out)?;

Is there any approach to make it universal for all serializable components in a World?

Ralith commented 1 year ago

There's no built in mechanism for that, since there's no graceful way to dynamically check for trait implementations on an arbitrary type in Rust. I recommend solving it at the application layer. You could e.g. use a declarative macro to reduce boilerplate if desired.

konceptosociala commented 1 year ago
#[macro_export]
macro_rules! world_serializer {
    ($ctx:ident, $($comp:ty),*) => {
        impl SerializeContext for $ctx {
            fn component_count(&self, archetype: &Archetype) -> usize {                
                archetype.component_types()
                    .filter(|&t|
                        $(
                            t == std::any::TypeId::of::<$comp>() ||
                        )*
                        false
                    )
                    .count()
            }

            fn serialize_component_ids<S: serde::ser::SerializeTuple>(
                &mut self,
                archetype: &Archetype,
                mut out: S,
            ) -> Result<S::Ok, S::Error> {
                $(
                    try_serialize_id::<$comp, _, _>(archetype, stringify!($comp), &mut out)?;
                )*

                out.end()
            }

            fn serialize_components<S: serde::ser::SerializeTuple>(
                &mut self,
                archetype: &Archetype,
                mut out: S,
            ) -> Result<S::Ok, S::Error> {
                $(
                    try_serialize::<$comp, _>(archetype, &mut out)?;
                )*

                out.end()
            }
        }

        impl DeserializeContext for $ctx {
            fn deserialize_component_ids<'de, A: serde::de::SeqAccess<'de>>(
                &mut self,
                mut seq: A,
            ) -> Result<ColumnBatchType, A::Error> {
                self.components.clear();
                let mut batch = ColumnBatchType::new();
                while let Some(id) = seq.next_element()? {
                    match id.as_str() {                        
                        $(                            
                            stringify!($comp) => {
                                batch.add::<$comp>();
                            }
                        )*

                        _ => {},
                    }
                    self.components.push(id);
                }

                Ok(batch)
            }

            fn deserialize_components<'de, A: serde::de::SeqAccess<'de>>(
                &mut self,
                entity_count: u32,
                mut seq: A,
                batch: &mut ColumnBatchBuilder,
            ) -> Result<(), A::Error> {
                for component in &self.components {
                    match component.as_str() {
                        $(                            
                            stringify!($comp) => {
                                deserialize_column::<$comp, _>(entity_count, &mut seq, batch)?;
                            }
                        )*

                        _ => {},
                    }
                }

                Ok(())
            }
        }
    }
}

It says

type must be known at this point
   |   cannot infer type of the type parameter `T` declared on the associated function `next_element`

   while let Some(id) = seq.next_element::<T>()? {
                                         +++++
  1. What should I specifiy as T?
  2. Am I able to use string literals/Strings as ComponentId (e.g. stringify!($comp))?
Ralith commented 1 year ago

What should I specifiy as T?

The type of your component IDs. In your case, perhaps &str or String?

Am I able to use string literals/Strings as ComponentId (e.g. stringify!($comp))?

Yep, any serializable type should work fine. I like to use enums, but strings make it easier to maintain forwards-compatibility, so they're not a bad choice.

konceptosociala commented 1 year ago

What should I specifiy as T?

The type of your component IDs. In your case, perhaps &str or String?

Am I able to use string literals/Strings as ComponentId (e.g. stringify!($comp))?

Yep, any serializable type should work fine. I like to use enums, but strings make it easier to maintain forwards-compatibility, so they're not a bad choice.

I also have thought so, and as saving world was successful, loading it made no error, but the world was just empty So I considered using String inappropriate

Ralith commented 1 year ago

Using String for component IDs is probably not the reason that happened.

konceptosociala commented 1 year ago

I am sorry, I've just forgotten to serialize Camera, so nothing appears on the screen :) Everything is working, thx for your job!