You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
With the introduction of an opt-in Component trait in #2254, external types can no longer be directly included as components.
In the standard scenario, we have 4 parties:
Bevy
the external crate (e.g. Rapier)
the interface crate (e.g. bevy_rapier)
the end user who just wants to make a game
Due to Rust's orphan rules, only Bevy or the external crate can implement Component for arbitrary external crate structs directly. The interface crate or end user can only bypass this using a wrapper type, and using a technique known as extension traits.
Users need to be able to use external types as components, in one form or another.
The external crate implements Component for the relevant types, possibly hidden behind a feature flag. This is the solution used for serde and other ecosystem crates. This is ideal, but not always going to happen, as it is outside of the control of anyone who cares about Bevy.
The interface crate or end user writes a manual wrapper type around the external component. This prevents accidental conflicts between different uses of the same component, but is heavy on boilerplate and conceptual indirection.
Bevy writes an ExternalComponent<T, StorageType> struct and implements component on it. This avoids ecosystem fragmentation, but has most of the same problems as 4. Extending this becomes increasingly onerous as we add more configuration to component types.
Given that 1 is impossible, and 2 is not enforceable, we are left with a choice between 3-5.
The tentative consensus from the active ECS contributors is that 3 is the least bad option, with the encouragement to use Deref and DerefMut on the wrapper types until delegation is one day (🤞🏽) added to Rust.
4 is a particularly bad solution due to the impact on type safety and difficulty extending it. 5 is less bad than 4 in some ways, as getting 4 correct is going to be frustrating for new users and its easy to screw up and make a completely unconfigurable wrapper type. However, by including an ExternalComponent type in Bevy itself, we are effectively endorsing that solution.
Thus we should:
clearly document how to include external component types in the documentation for both end users and plugin authors
investigate disabling the naive use of 4 by forbidding unconstrained generic derives of the Component trait (credit to @DJMcNab)
investigate publishing a tool / crate to more quickly allow users to wrap types in Bevy components (and resources, as the same issues exists for Reflect even without a Resource trait), credit to @BoxyUwU
With the introduction of an opt-in
Componenttrait in #2254, external types can no longer be directly included as components.In the standard scenario, we have 4 parties:
Due to Rust's orphan rules, only Bevy or the external crate can implement
Componentfor arbitrary external crate structs directly. The interface crate or end user can only bypass this using a wrapper type, and using a technique known as extension traits.Users need to be able to use external types as components, in one form or another.
There are several options for this:
Componentfor external types. This is impossible, as this set is unknowable, and using overly broad blanket impls causes all of the issues that [Merged by Bors] - Implement and require#[derive(Component)]on all component structs #2254 solved.Componentfor the relevant types, possibly hidden behind a feature flag. This is the solution used forserdeand other ecosystem crates. This is ideal, but not always going to happen, as it is outside of the control of anyone who cares about Bevy.ExternalComponent<T, StorageType>struct and implements component on it. This avoids accidental conflicts and reduces boilerplate, but reduces the type safety of the API in some of the ways that [Merged by Bors] - Implement and require#[derive(Component)]on all component structs #2254 solved.ExternalComponent<T, StorageType>struct and implements component on it. This avoids ecosystem fragmentation, but has most of the same problems as 4. Extending this becomes increasingly onerous as we add more configuration to component types.Given that 1 is impossible, and 2 is not enforceable, we are left with a choice between 3-5.
The tentative consensus from the active ECS contributors is that 3 is the least bad option, with the encouragement to use
DerefandDerefMuton the wrapper types until delegation is one day (🤞🏽) added to Rust.4 is a particularly bad solution due to the impact on type safety and difficulty extending it. 5 is less bad than 4 in some ways, as getting 4 correct is going to be frustrating for new users and its easy to screw up and make a completely unconfigurable wrapper type. However, by including an
ExternalComponenttype in Bevy itself, we are effectively endorsing that solution.Thus we should:
Reflecteven without aResourcetrait), credit to @BoxyUwU