bevyengine / bevy

A refreshingly simple data-driven game engine built in Rust
https://bevyengine.org
Apache License 2.0
35.52k stars 3.52k forks source link

Add trait ComponentConstructor #15559

Open Qinka opened 3 days ago

Qinka commented 3 days ago

What problem does this solve or what need does it fill?

For rapier and avian, Collider is not implemented with Reflect. We cannot use extra properties in GLFT to reflect to create components.

Can we add a ComponentConstructor or something else to create a component from a reflectable constructor.

What solution would you like?

Allow Component convert from another reflectable type

#[derive(Component)]
struct Foo {
   ....
}

impl Foo {
  pub fn new_a (...) { ... }
  pub fn new_b (...) { ... }
}

#[derive(Reflect, ComponentConstructor)]
enum FooConstructor {
  A( ... ),
  B( ... ),
}

impl ComponentConstructor for FooConstructor {
  type C = Foo;
  pub fn to_component(self) -> C {
    ....
  }
}

....

// for spawn

commands.spawn(( FooConstructor::A( .... ), ))
// should same with
commands.spawn(( Foo::new_a( ... ), ))

What alternative(s) have you considered?

For avian, they use https://github.com/Jondolf/avian/blob/39a7480cd1915bc5266aa8ebd3c3436d4d0568b0/src/collision/collider/backend.rs#L358C4-L358C30 to convert Constructor to Component.

atornity commented 3 days ago

For context: https://github.com/bevyengine/bevy/discussions/14437 (section called "Construct"), this is coming but I believe it will be called 'Construct' instead of 'ComponentConstructor'

SkiFire13 commented 3 days ago

It's not clear to me what commands.spawn(( FooConstructor::A( .... ), )) would gain you over doing commands.spawn(( FooConstructor::A( .... ).to_component(), )) and directly spawning the component.

For avian, they use https://github.com/Jondolf/avian/blob/39a7480cd1915bc5266aa8ebd3c3436d4d0568b0/src/collision/collider/backend.rs#L358C4-L358C30 to convert Constructor to Component.

Avian seems to need this to use this in order to create colliders that need to read from the Mesh associated to that entity. It's not clear how your proposed ComponentConstructor could handle this, since its to_component method takes no context other than the constructor itself.


Another alternative would be using hooks to detect the insertion of the constructor and immediately replace it with the constructed component. However an issue that this has (and also Construct) is that this will break if you do .insert(constructor).insert(dependency) (or .construct::<Foo>(constructor).insert(dependency)) because the constructor will run when the dependency has not been inserted yet, and hence will fail. The current approach with a delayed query will avoid this issue, at the cost of some systems being able to observe the constructor before it gets replaced by the constructed value.