Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Required Components #14791

Merged
merged 21 commits into from
Aug 27, 2024
Merged

Required Components #14791

merged 21 commits into from
Aug 27, 2024

Conversation

cart
Copy link
Member

@cart cart commented Aug 17, 2024

Introduction

This is the first step in my Next Generation Scene / UI Proposal.

Fixes #7272 #14800.

Bevy's current Bundles as the "unit of construction" hamstring the UI user experience and have been a pain point in the Bevy ecosystem generally when composing scenes:

  • They are an additional object defining concept, which must be learned separately from components. Notably, Bundles are not present at runtime, which is confusing and limiting.
  • They can completely erase the defining component during Bundle init. For example, ButtonBundle { style: Style::default(), ..default() } makes no mention of the Button component symbol, which is what makes the Entity a "button"!
  • They are not capable of representing "dependency inheritance" without completely non-viable / ergonomically crushing nested bundles. This limitation is especially painful in UI scenarios, but it applies to everything across the board.
  • They introduce a bunch of additional nesting when defining scenes, making them ugly to look at
  • They introduce component name "stutter": SomeBundle { component_name: ComponentName::new() }
  • They require copious sprinklings of ..default() when spawning them in Rust code, due to the additional layer of nesting

Required Components solve this by allowing you to define which components a given component needs, and how to construct those components when they aren't explicitly provided.

This is what a ButtonBundle looks like with Bundles (the current approach):

#[derive(Component, Default)]
struct Button;

#[derive(Bundle, Default)]
struct ButtonBundle {
    pub button: Button,
    pub node: Node,
    pub style: Style,
    pub interaction: Interaction,
    pub focus_policy: FocusPolicy,
    pub border_color: BorderColor,
    pub border_radius: BorderRadius,
    pub image: UiImage,
    pub transform: Transform,
    pub global_transform: GlobalTransform,
    pub visibility: Visibility,
    pub inherited_visibility: InheritedVisibility,
    pub view_visibility: ViewVisibility,
    pub z_index: ZIndex,
}

commands.spawn(ButtonBundle {
    style: Style {
        width: Val::Px(100.0),
        height: Val::Px(50.0),
        ..default()
    },
    focus_policy: FocusPolicy::Block,
    ..default()
})

And this is what it looks like with Required Components:

#[derive(Component)]
#[require(Node, UiImage)]
struct Button;

commands.spawn((
    Button,
    Style { 
        width: Val::Px(100.0),
        height: Val::Px(50.0),
        ..default()
    },
    FocusPolicy::Block,
));

With Required Components, we mention only the most relevant components. Every component required by Node (ex: Style, FocusPolicy, etc) is automatically brought in!

Efficiency

  1. At insertion/spawn time, Required Components (including recursive required components) are initialized and inserted as if they were manually inserted alongside the given components. This means that this is maximally efficient: there are no archetype or table moves.
  2. Required components are only initialized and inserted if they were not manually provided by the developer. For the code example in the previous section, because Style and FocusPolicy are inserted manually, they will not be initialized and inserted as part of the required components system. Efficient!
  3. The "missing required components and constructors needed for an insertion" are cached in the "archetype graph edge", meaning they aren't computed per-insertion. When a component is inserted, the "missing required components" list is iterated (and that graph edge (AddBundle) is actually already looked up for us during insertion, because we need that for "normal" insert logic too).

IDE Integration

The #[require(SomeComponent)] macro has been written in such a way that Rust Analyzer can provide type-inspection-on-hover and F12 / go-to-definition for required components.

Custom Constructors

The require syntax expects a Default constructor by default, but it can be overridden with a custom constructor:

#[derive(Component)]
#[require(
    Node,
    Style(button_style),
    UiImage
)]
struct Button;

fn button_style() -> Style {
    Style {
        width: Val::Px(100.0),
        ..default()
    }
}

Multiple Inheritance

You may have noticed by now that this behaves a bit like "multiple inheritance". One of the problems that this presents is that it is possible to have duplicate requires for a given type at different levels of the inheritance tree:

#[derive(Component)
struct X(usize);

#[derive(Component)]
#[require(X(x1))
struct Y;

fn x1() -> X {
    X(1)
}

#[derive(Component)]
#[require(
    Y,
    X(x2),
)]
struct Z;

fn x2() -> X {
    X(2)
}

// What version of X is inserted for Z?
commands.spawn(Z);

This is allowed (and encouraged), although this doesn't appear to occur much in practice. First: only one version of X is initialized and inserted for Z. In the case above, I think we can all probably agree that it makes the most sense to use the x2 constructor for X, because Y's x1 constructor exists "beneath" Z in the inheritance hierarchy; Z's constructor is "more specific".

The algorithm is simple and predictable:

  1. Use all of the constructors (including default constructors) directly defined in the spawned component's require list
  2. In the order the requires are defined in #[require()], recursively visit the require list of each of the components in the list (this is a depth Depth First Search). When a constructor is found, it will only be used if one has not already been found.

From a user perspective, just think about this as the following:

  1. Specifying a required component constructor for Foo directly on a spawned component Bar will result in that constructor being used (and overriding existing constructors lower in the inheritance tree). This is the classic "inheritance override" behavior people expect.
  2. For cases where "multiple inheritance" results in constructor clashes, Components should be listed in "importance order". List a component earlier in the requirement list to initialize its inheritance tree earlier.

Required Components does generally result in a model where component values are decoupled from each other at construction time. Notably, some existing Bundle patterns use bundle constructors to initialize multiple components with shared state. I think (in general) moving away from this is necessary:

  1. It allows Required Components (and the Scene system more generally) to operate according to simple rules
  2. The "do arbitrary init value sharing in Bundle constructors" approach already causes data consistency problems, and those problems would be exacerbated in the context of a Scene/UI system. For cases where shared state is truly necessary, I think we are better served by observers / hooks.
  3. If a situation truly needs shared state constructors (which should be rare / generally discouraged), Bundles are still there if they are needed.

Next Steps

  • Require Construct-ed Components: I have already implemented this (as defined in the Next Generation Scene / UI Proposal. However I've removed Construct support from this PR, as that has not landed yet. Adding this back in requires relatively minimal changes to the current impl, and can be done as part of a future Construct pr.
  • Port Built-in Bundles to Required Components: This isn't something we should do right away. It will require rethinking our public interfaces, which IMO should be done holistically after the rest of Next Generation Scene / UI lands. I think we should merge this PR first and let people experiment inside their own code with their own Components while we wait for the rest of the new scene system to land.
  • Consider Automatic Required Component Removal: We should evaluate if automatic Required Component removal should be done. Ex: if all components that explicitly require a component are removed, automatically remove that component. This issue has been explicitly deferred in this PR, as I consider the insertion behavior to be desirable on its own (and viable on its own). I am also doubtful that we can find a design that has behavior we actually want. Aka: can we really distinguish between a component that is "only there because it was automatically inserted" and "a component that was necessary / should be kept". See my discussion response here for more details.

@cart cart added C-Feature A new feature, making something new possible A-ECS Entities, components, systems, and events X-Controversial There is active debate or serious implications around merging this PR labels Aug 17, 2024
@cart cart added this to the 0.15 milestone Aug 17, 2024
@iiYese iiYese self-requested a review August 17, 2024 01:27
@alice-i-cecile alice-i-cecile added the S-Needs-Review Needs reviewer attention (from anyone!) to move forward label Aug 17, 2024
Copy link
Member

@alice-i-cecile alice-i-cecile left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I really like the end result, but have a few nits on internal clarity. I'd also really like to ship this with good docs demonstrating the various features, probably on the Component trait.

@alice-i-cecile
Copy link
Member

One more question: it looks like the fallback constructor is Default. Why not make it FromWorld for both consistency and more expressiveness?

@james-j-obrien
Copy link
Contributor

james-j-obrien commented Aug 17, 2024

Seems like this doesn't work with hooks/observers as the required components aren't in the added array in BundleInfo (which is the current source of truth).

Here's a test that fails:

#[test]
fn required_components_hooks() {
    #[derive(Component)]
    #[require(Y)]
    struct X;

    #[derive(Component, Default)]
    struct Y;

    #[derive(Resource)]
    struct R(usize);

    let mut world = World::new();
    world.insert_resource(R(0));
    world
        .register_component_hooks::<Y>()
        .on_add(|mut world, _, _| world.resource_mut::<R>().0 += 1);

    // Spawn entity and ensure Y was added
    assert!(world.spawn(X).contains::<Y>());

    assert_eq!(world.resource::<R>().0, 1);
}

@cart
Copy link
Member Author

cart commented Aug 17, 2024

One more question: it looks like the fallback constructor is Default. Why not make it FromWorld for both consistency and more expressiveness?

Given that the constructors are called mid-insertion, this is dangerous. FromWorld could in theory access data that shouldn't be accessed during an insert. Ex: try to read components before they are initialized.

In theory I suspect we would be able to do this via careful ordering of operations, but it would also require some serious restructuring of entity initialization (and how we break up / borrow components from world), especially if we don't want to redundantly allocate outside of the "real" storage.

Worth considering, but imo as a separate PR, and with much more meticulous soundness analysis.

@cart
Copy link
Member Author

cart commented Aug 17, 2024

Seems like this doesn't work with hooks/observers as the required components aren't in the added array in BundleInfo (which is the current source of truth).

Hmm yeah I'll look into this.

@tychedelia
Copy link
Contributor

Confirmed that go to definition works in rust-analyzer:

image

Unfortunately, it does not work in RustRover :(
image

Not sure if there's prior art here that's already on JetBrains radar, but it might be worth filing an issue if not.

@TotalKrill
Copy link
Contributor

TotalKrill commented Aug 17, 2024

I have a question regarding the ergnomics, using the Button example provided.

How in the following example would I find out that I can/need to spawn Style to change the look of my spawned Button, neither the Docs or the Language server would help me in this example.
Traversing the tree seems like an easy way to get lost or walk in circles quite quickly...

  • Button
    • Node
      • NodeReq1
      • NodeReq2,
    • UiImage
      • UiImageReq2,
      • UiImageReq1
      • Node
#[derive(Component)]
#[require(Node, UiImage)]
struct Button;

commands.spawn((
    Button,
    Style { 
        width: Val::Px(100.0),
        height: Val::Px(50.0),
        ..default()
    },
    FocusPolicy::Block,

crates/bevy_ecs/macros/src/component.rs Show resolved Hide resolved
crates/bevy_ecs/macros/src/component.rs Outdated Show resolved Hide resolved
crates/bevy_ecs/macros/src/component.rs Outdated Show resolved Hide resolved
@Zeenobit
Copy link
Contributor

Thank you for this! 🚀

Does this implementation allow users to query the required components of a component at runtime?

Context:
In Moonshine Kind, I use a CastInto<T> trait which has to be implemented manually to allow "upcasting" safely between related components (i.e. a Button should always have a Node, so an Instance<Button> is safely convertible to Instance<Node>).

If we can query the required components, safe casting may be doable automatically.

Copy link
Contributor

@NthTensor NthTensor left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Wow, this is really well done. The docs on this are especially high quality. Thank you for prioritizing this section of the bsn work.

It looks like the notes on multiple inheritance didn't really make it into the docs. Could we copy over the explanation from the PR description?

@alice-i-cecile alice-i-cecile added S-Ready-For-Final-Review This PR has been approved by the community. It's ready for a maintainer to consider merging it and removed S-Needs-Review Needs reviewer attention (from anyone!) to move forward labels Aug 26, 2024
@cart
Copy link
Member Author

cart commented Aug 26, 2024

It looks like the notes on multiple inheritance didn't really make it into the docs. Could we copy over the explanation from the PR description?

Done!

@alice-i-cecile
Copy link
Member

@cart feel free to merge once CI is green: the new docs look good and it looks like you've resolved the remaining concerns for the MVP.

Copy link
Contributor

@killercup killercup left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added suggestion for what CI complained about

crates/bevy_ecs/src/component.rs Outdated Show resolved Hide resolved
Co-authored-by: Pascal Hertleif <killercup@gmail.com>
@cart cart added this pull request to the merge queue Aug 27, 2024
Merged via the queue into bevyengine:main with commit 9cdb915 Aug 27, 2024
26 checks passed
github-merge-queue bot pushed a commit that referenced this pull request Oct 3, 2024
…d component (#15026)

## Objective
The new Required Components feature (#14791) in Bevy allows spawning a
fixed set of components with a single method with cool require macro.
However, there's currently no corresponding method to remove all those
components together. This makes it challenging to keep insertion and
removal code in sync, especially for simple using cases.
```rust
#[derive(Component)]
#[require(Y)]
struct X;

#[derive(Component, Default)]
struct Y;

world.entity_mut(e).insert(X); // Spawns both X and Y
world.entity_mut(e).remove::<X>(); 
world.entity_mut(e).remove::<Y>(); // We need to manually remove dependencies without any sync with the `require` macro
```
## Solution
Simplifies component management by providing operations for removal
required components.
This PR introduces simple 'footgun' methods to removes all components of
this bundle and its required components.

Two new methods are introduced:
For Commands:
```rust
commands.entity(e).remove_with_requires::<B>();
```
For World:
```rust
world.entity_mut(e).remove_with_requires::<B>();
```

For performance I created new field in Bundels struct. This new field
"contributed_bundle_ids" contains cached ids for dynamic bundles
constructed from bundle_info.cintributed_components()

## Testing
The PR includes three test cases:

1. Removing a single component with requirements using World.
2. Removing a bundle with requirements using World.
3. Removing a single component with requirements using Commands.
4. Removing a single component with **runtime** requirements using
Commands

These tests ensure the feature works as expected across different
scenarios.

## Showcase
Example:
```rust
use bevy_ecs::prelude::*;

#[derive(Component)]
#[require(Y)]
struct X;

#[derive(Component, Default)]
#[require(Z)]
struct Y;

#[derive(Component, Default)]
struct Z;

#[derive(Component)]
struct W;

let mut world = World::new();

// Spawn an entity with X, Y, Z, and W components
let entity = world.spawn((X, W)).id();

assert!(world.entity(entity).contains::<X>());
assert!(world.entity(entity).contains::<Y>());
assert!(world.entity(entity).contains::<Z>());
assert!(world.entity(entity).contains::<W>());

// Remove X and required components Y, Z
world.entity_mut(entity).remove_with_requires::<X>();

assert!(!world.entity(entity).contains::<X>());
assert!(!world.entity(entity).contains::<Y>());
assert!(!world.entity(entity).contains::<Z>());

assert!(world.entity(entity).contains::<W>());
```

## Motivation for PR
#15580 

## Performance

I made simple benchmark
```rust
let mut world = World::default();
let entity = world.spawn_empty().id();

let steps = 100_000_000;

let start = std::time::Instant::now();
for _ in 0..steps {
    world.entity_mut(entity).insert(X);
    world.entity_mut(entity).remove::<(X, Y, Z, W)>();
}
let end = std::time::Instant::now();
println!("normal remove: {:?} ", (end - start).as_secs_f32());
println!("one remove: {:?} micros", (end - start).as_secs_f64() / steps as f64 * 1_000_000.0);

let start = std::time::Instant::now();
for _ in 0..steps {
    world.entity_mut(entity).insert(X);
    world.entity_mut(entity).remove_with_requires::<X>();
}
let end = std::time::Instant::now();
println!("remove_with_requires: {:?} ", (end - start).as_secs_f32());
println!("one remove_with_requires: {:?} micros", (end - start).as_secs_f64() / steps as f64 * 1_000_000.0);
```

Output:

CPU: Amd Ryzen 7 2700x

```bash
normal remove: 17.36135 
one remove: 0.17361348299999999 micros
remove_with_requires: 17.534006 
one remove_with_requires: 0.17534005400000002 micros
```

NOTE: I didn't find any tests or mechanism in the repository to update
BundleInfo after creating new runtime requirements with an existing
BundleInfo. So this PR also does not contain such logic.

## Future work (outside this PR)

Create cache system for fast removing components in "safe" mode, where
"safe" mode is remove only required components that will be no longer
required after removing root component.

---------

Co-authored-by: a.yamaev <a.yamaev@smartengines.com>
Co-authored-by: Carter Anderson <mcanders1@gmail.com>
ItsDoot pushed a commit to ItsDoot/bevy that referenced this pull request Oct 4, 2024
…d component (bevyengine#15026)

## Objective
The new Required Components feature (bevyengine#14791) in Bevy allows spawning a
fixed set of components with a single method with cool require macro.
However, there's currently no corresponding method to remove all those
components together. This makes it challenging to keep insertion and
removal code in sync, especially for simple using cases.
```rust
#[derive(Component)]
#[require(Y)]
struct X;

#[derive(Component, Default)]
struct Y;

world.entity_mut(e).insert(X); // Spawns both X and Y
world.entity_mut(e).remove::<X>(); 
world.entity_mut(e).remove::<Y>(); // We need to manually remove dependencies without any sync with the `require` macro
```
## Solution
Simplifies component management by providing operations for removal
required components.
This PR introduces simple 'footgun' methods to removes all components of
this bundle and its required components.

Two new methods are introduced:
For Commands:
```rust
commands.entity(e).remove_with_requires::<B>();
```
For World:
```rust
world.entity_mut(e).remove_with_requires::<B>();
```

For performance I created new field in Bundels struct. This new field
"contributed_bundle_ids" contains cached ids for dynamic bundles
constructed from bundle_info.cintributed_components()

## Testing
The PR includes three test cases:

1. Removing a single component with requirements using World.
2. Removing a bundle with requirements using World.
3. Removing a single component with requirements using Commands.
4. Removing a single component with **runtime** requirements using
Commands

These tests ensure the feature works as expected across different
scenarios.

## Showcase
Example:
```rust
use bevy_ecs::prelude::*;

#[derive(Component)]
#[require(Y)]
struct X;

#[derive(Component, Default)]
#[require(Z)]
struct Y;

#[derive(Component, Default)]
struct Z;

#[derive(Component)]
struct W;

let mut world = World::new();

// Spawn an entity with X, Y, Z, and W components
let entity = world.spawn((X, W)).id();

assert!(world.entity(entity).contains::<X>());
assert!(world.entity(entity).contains::<Y>());
assert!(world.entity(entity).contains::<Z>());
assert!(world.entity(entity).contains::<W>());

// Remove X and required components Y, Z
world.entity_mut(entity).remove_with_requires::<X>();

assert!(!world.entity(entity).contains::<X>());
assert!(!world.entity(entity).contains::<Y>());
assert!(!world.entity(entity).contains::<Z>());

assert!(world.entity(entity).contains::<W>());
```

## Motivation for PR
bevyengine#15580 

## Performance

I made simple benchmark
```rust
let mut world = World::default();
let entity = world.spawn_empty().id();

let steps = 100_000_000;

let start = std::time::Instant::now();
for _ in 0..steps {
    world.entity_mut(entity).insert(X);
    world.entity_mut(entity).remove::<(X, Y, Z, W)>();
}
let end = std::time::Instant::now();
println!("normal remove: {:?} ", (end - start).as_secs_f32());
println!("one remove: {:?} micros", (end - start).as_secs_f64() / steps as f64 * 1_000_000.0);

let start = std::time::Instant::now();
for _ in 0..steps {
    world.entity_mut(entity).insert(X);
    world.entity_mut(entity).remove_with_requires::<X>();
}
let end = std::time::Instant::now();
println!("remove_with_requires: {:?} ", (end - start).as_secs_f32());
println!("one remove_with_requires: {:?} micros", (end - start).as_secs_f64() / steps as f64 * 1_000_000.0);
```

Output:

CPU: Amd Ryzen 7 2700x

```bash
normal remove: 17.36135 
one remove: 0.17361348299999999 micros
remove_with_requires: 17.534006 
one remove_with_requires: 0.17534005400000002 micros
```

NOTE: I didn't find any tests or mechanism in the repository to update
BundleInfo after creating new runtime requirements with an existing
BundleInfo. So this PR also does not contain such logic.

## Future work (outside this PR)

Create cache system for fast removing components in "safe" mode, where
"safe" mode is remove only required components that will be no longer
required after removing root component.

---------

Co-authored-by: a.yamaev <a.yamaev@smartengines.com>
Co-authored-by: Carter Anderson <mcanders1@gmail.com>
robtfm pushed a commit to robtfm/bevy that referenced this pull request Oct 4, 2024
…d component (bevyengine#15026)

## Objective
The new Required Components feature (bevyengine#14791) in Bevy allows spawning a
fixed set of components with a single method with cool require macro.
However, there's currently no corresponding method to remove all those
components together. This makes it challenging to keep insertion and
removal code in sync, especially for simple using cases.
```rust
#[derive(Component)]
#[require(Y)]
struct X;

#[derive(Component, Default)]
struct Y;

world.entity_mut(e).insert(X); // Spawns both X and Y
world.entity_mut(e).remove::<X>(); 
world.entity_mut(e).remove::<Y>(); // We need to manually remove dependencies without any sync with the `require` macro
```
## Solution
Simplifies component management by providing operations for removal
required components.
This PR introduces simple 'footgun' methods to removes all components of
this bundle and its required components.

Two new methods are introduced:
For Commands:
```rust
commands.entity(e).remove_with_requires::<B>();
```
For World:
```rust
world.entity_mut(e).remove_with_requires::<B>();
```

For performance I created new field in Bundels struct. This new field
"contributed_bundle_ids" contains cached ids for dynamic bundles
constructed from bundle_info.cintributed_components()

## Testing
The PR includes three test cases:

1. Removing a single component with requirements using World.
2. Removing a bundle with requirements using World.
3. Removing a single component with requirements using Commands.
4. Removing a single component with **runtime** requirements using
Commands

These tests ensure the feature works as expected across different
scenarios.

## Showcase
Example:
```rust
use bevy_ecs::prelude::*;

#[derive(Component)]
#[require(Y)]
struct X;

#[derive(Component, Default)]
#[require(Z)]
struct Y;

#[derive(Component, Default)]
struct Z;

#[derive(Component)]
struct W;

let mut world = World::new();

// Spawn an entity with X, Y, Z, and W components
let entity = world.spawn((X, W)).id();

assert!(world.entity(entity).contains::<X>());
assert!(world.entity(entity).contains::<Y>());
assert!(world.entity(entity).contains::<Z>());
assert!(world.entity(entity).contains::<W>());

// Remove X and required components Y, Z
world.entity_mut(entity).remove_with_requires::<X>();

assert!(!world.entity(entity).contains::<X>());
assert!(!world.entity(entity).contains::<Y>());
assert!(!world.entity(entity).contains::<Z>());

assert!(world.entity(entity).contains::<W>());
```

## Motivation for PR
bevyengine#15580 

## Performance

I made simple benchmark
```rust
let mut world = World::default();
let entity = world.spawn_empty().id();

let steps = 100_000_000;

let start = std::time::Instant::now();
for _ in 0..steps {
    world.entity_mut(entity).insert(X);
    world.entity_mut(entity).remove::<(X, Y, Z, W)>();
}
let end = std::time::Instant::now();
println!("normal remove: {:?} ", (end - start).as_secs_f32());
println!("one remove: {:?} micros", (end - start).as_secs_f64() / steps as f64 * 1_000_000.0);

let start = std::time::Instant::now();
for _ in 0..steps {
    world.entity_mut(entity).insert(X);
    world.entity_mut(entity).remove_with_requires::<X>();
}
let end = std::time::Instant::now();
println!("remove_with_requires: {:?} ", (end - start).as_secs_f32());
println!("one remove_with_requires: {:?} micros", (end - start).as_secs_f64() / steps as f64 * 1_000_000.0);
```

Output:

CPU: Amd Ryzen 7 2700x

```bash
normal remove: 17.36135 
one remove: 0.17361348299999999 micros
remove_with_requires: 17.534006 
one remove_with_requires: 0.17534005400000002 micros
```

NOTE: I didn't find any tests or mechanism in the repository to update
BundleInfo after creating new runtime requirements with an existing
BundleInfo. So this PR also does not contain such logic.

## Future work (outside this PR)

Create cache system for fast removing components in "safe" mode, where
"safe" mode is remove only required components that will be no longer
required after removing root component.

---------

Co-authored-by: a.yamaev <a.yamaev@smartengines.com>
Co-authored-by: Carter Anderson <mcanders1@gmail.com>
@Jaso333
Copy link

Jaso333 commented Oct 8, 2024

I've joined the party a bit late here, but I'm wondering how this affects a certain pattern I'm using.

I'm currently using populated bundles as templates for specific types of entities, e.g. I have a resource: EnemyTemplate(EnemyBundle) which gets created by a system at some point after the assets have been loaded; it inserts the resource into the world containing the bundle with the correct sprite image and other configuration from the world. This is kind of like my own rudimentary "prefab" pattern. If the idea of "bundles" is deprecated, I don't really know how to achieve this, or at least it isn't clear to me. I understand that bundles are still here to stay, but I'd like to avoid them if I can in favour of whatever the new way is of doing this.

@NthTensor
Copy link
Contributor

NthTensor commented Oct 8, 2024

Bundles themselves are not deprecated, but the existing bundles are being removed from the engine's public api. Required components is actually kind of built on bundles.

In the future, the recommended approach to this will be through BSN. For now, you may have to write some custom bundles for yourself.

@Jaso333
Copy link

Jaso333 commented Oct 8, 2024

Understood. At least I won't have bundle trees like I currently have, e.g. EnemyBundle contains a field for SpriteBundle.

@Zeenobit
Copy link
Contributor

Are component requirements enforced during scene deserialization?

i.e. if component A requires B, and I load a scene with just an A, would B get added dynamically?

@alice-i-cecile
Copy link
Member

Yes :)

@alice-i-cecile
Copy link
Member

Thank you to everyone involved with the authoring or reviewing of this PR! This work is relatively important and needs release notes! Head over to bevyengine/bevy-website#1725 if you'd like to help out.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
A-ECS Entities, components, systems, and events C-Feature A new feature, making something new possible M-Needs-Release-Note Work that should be called out in the blog due to impact S-Ready-For-Final-Review This PR has been approved by the community. It's ready for a maintainer to consider merging it X-Blessed Has a large architectural impact or tradeoffs, but the design has been endorsed by decision makers
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Required Components