FyroxEngine / Fyrox

3D and 2D game engine written in Rust
https://fyrox.rs
MIT License
7.64k stars 343 forks source link

Add proc macro for property inheritance #354

Closed mrDIMAS closed 2 years ago

mrDIMAS commented 2 years ago

Property inheritance is a mechanism, that allows you to inherit property values from base scene (or transitively, through chain of scenes). For example you have SceneA prefab with ObjectA inside, while ObjectA have foo property. Then you create another scene and instantiate SceneA there. Now you have a copy of ObjectA, let's call it ObjectA*. It now have the same value of foo property. Then you change foo property in ObjectA on SceneA to some other value. The engine then will take this new value and set it to foo property of ObjectA*. This will happen all the time you load a scene with instances, but if you change foo value in ObjectA*, it will remain unchanged. More info can be found here

To track changes in properties, the engine uses TemplateVariable wrapper struct which adds flags storage to the property it wraps. When you change a property wrapped in TemplateVariable, it automatically marks value as modified. This information is then used to fetch values from base properties.

The engine uses a list of TemplateVariables that is provided by DirectlyInheritableEntity trait. Implementing this trait involves some boilerplate code, that can be easily generated by proc macro. Currently, to reduce amount of boilerplate code, the engine offsers impl_directly_inheritable_entity_trait macro. An example of its usage can be found here

I propose to create a Inherit proc-macro, that will generate similar implementation that impl_directly_inheritable_entity_trait does. It should include only fields that explicitly marked with #[inherit] macro.

A simple code example:

use crate::{
    core::{
        reflect::Reflect,
        variable::{InheritableVariable, TemplateVariable},
    },
    scene::DirectlyInheritableEntity,
};

#[derive(Inherit, Reflect)]
struct Foo {
    #[inherit]
    inheritable_field: TemplateVariable<f32>,

    // Non-inheritable, because does not marked with #[inherit] attribute
    other_field: String,
}

// This should be generated.
impl DirectlyInheritableEntity for Foo {
    fn inheritable_properties_ref(&self) -> Vec<&dyn InheritableVariable> {
        vec![&self.inheritable_field]
    }

    fn inheritable_properties_mut(&mut self) -> Vec<&mut dyn InheritableVariable> {
        vec![&mut self.inheritable_field]
    }
}
mrDIMAS commented 2 years ago

@toyboot4e, your excellent proc-macro skills can be very useful here 🙏

toyboot4e commented 2 years ago

Thank you for calling me! I will do it today.

By the way, DirectlyInheritableEntity might want to return slices (&[&dyn Trait] and &mut [&mut dyn Trait]). I'd first make a Vec version and let's change when slices turned out to be better.

mrDIMAS commented 2 years ago

Yeah, it probably can be changed to returns a slice, I can't remember why I made it return a Vec 🤔 .

toyboot4e commented 2 years ago

Oh, cannot return reference to temporary value. Maybe Vec<T> vs Box<[T]> is the proper question. And Vec<T> looks better in that case!