Migration Guides

Migration Guide: 0.16 to 0.17

The most important changes to be aware of this release are:

Handle::Weak has been replaced by Handle::Uuid. #

PRs:#19896

Handle::Weak had some weird behavior. It allowed for a sprite to be given a handle that is dropped while the sprite is still using it. This also resulted in more complexity in the asset system. The primary remaining use for Handle::Weak is to store asset UUIDs initialized through the weak_handle! macro. To address this, Handle::Weak has been replaced by Handle::Uuid!

Users using the weak_handle! macro should switch to the uuid_handle! macro.

// Before
const IMAGE: Handle<Image> = weak_handle!("b20988e9-b1b9-4176-b5f3-a6fa73aa617f");

// After
const IMAGE: Handle<Image> = uuid_handle!("b20988e9-b1b9-4176-b5f3-a6fa73aa617f");

Users using Handle::clone_weak can (most likely) just call Handle::clone instead.

// Somewhere in some startup system.
let my_sprite_image = asset_server.load("monster.png");

// In game code...
// This sprite could be unloaded even if the sprite is still using it!
commands.spawn(Sprite::from_image(my_sprite_image.clone_weak()));

// Just do this instead!
commands.spawn(Sprite::from_image(my_sprite_image.clone()));

Users using the Handle::Weak variant directly should consider replacing it with AssetId instead, accessible through Handle::id. These situations are very case-by-case migrations.

P.S., for users of the weak_handle! macro: If you are using it for shaders, consider switching to load_shader_library/load_embedded_asset instead (especially replacing load_internal_asset). This enables hot reloading for your shaders - which Bevy internally has done this cycle!

Deprecate iter_entities and iter_entities_mut. #

PRs:#20260

In Bevy 0.17.0 we deprecate world.iter_entities() and world.iter_entities_mut(). Use world.query::<EntityMut>().iter(&world) and world.query::<EntityRef>().iter(&mut world) instead.

This may not return every single entity, because of default filter queries. If you really intend to query disabled entities too, consider removing the DefaultQueryFilters resource from the world before querying the elements. You can also add an Allow<Component> filter to allow a specific disabled Component to show up in the query.

RelativeCursorPosition is now object-centered #

PRs:#16615

When picking objects, RelativeCursorPosition's coordinates are now object-centered with (0,0) at the center of the node and the corners at (±0.5, ±0.5). Its normalized_visible_node_rect field has been removed and replaced with a new cursor_over: bool field which is set to true when the cursor is hovering a visible section of the UI node.

Text2d moved to bevy_sprite #

PRs:#20594

The world-space text types Text2d and Text2dShadow have been moved to the bevy_sprite crate, along with their associated systems. Import them directly or from bevy::sprite now.

SyncCell and SyncUnsafeCell moved to bevy_platform #

PRs:#19305

bevy_utils::synccell::SyncCell is now bevy_platform::cell::SyncCell bevy_utils::syncunsafecell::SyncUnsafeCell is now bevy_platform::cell::SyncUnsafeCell

Renamed Condition to SystemCondition #

PRs:#19328

The Condition trait is now called SystemCondition. Replace all references and imports.

This change was made because Condition is an overly generic name that collides too often and is rarely used directly, despite appearing in the prelude.

Observers and one-shot systems are now marked as Internal #

PRs:#20204

Bevy 0.17 introduces internal entities. Entities tagged by the Internal component that are hidden from most queries using DefaultQueryFilters.

Currently, both Observers and systems that are registered through World::register_system are considered internal entities.

If you queried them before, add the Allow<Internal> filter to the query to bypass the default filter.

SceneSpawner methods have been renamed and replaced #

PRs:#18358

Some methods on SceneSpawner have been renamed: - despawn -> despawn_dynamic - despawn_sync -> despawn_dynamic_sync - update_spawned_scenes -> update_spawned_dynamic_scenes

In their place, we've added despawn, despawn_sync, and update_spawned_scenes which all act on Scenes (as opposed to DynamicScenes).

Location is no longer a Component #

PRs:#19306

bevy_picking::Location was erroneously made a Component. It is no longer one. bevy_picking::PointerLocation wraps a Location and is the component you should be using instead.

Remove scale_value #

PRs:#19143

The scale_value function from bevy::text::text2d has been removed. Multiply by the scale factor instead.

Assets::insert and Assets::get_or_insert_with now return Result #

PRs:#20439

In previous versions of Bevy, there was a bug where inserting an asset into an AssetId whose handle was dropped would result in a panic. Now this is an error! Calling Assets::insert and Assets::get_or_insert_with returns an error you can inspect.

To match the previous behavior, just unwrap() the result.

Enable Wayland by default #

PRs:#19232

Wayland has now been added to the default features of the bevy crate.

  called `Result::unwrap()` on an `Err` value:
  pkg-config exited with status code 1
  > PKG_CONFIG_ALLOW_SYSTEM_LIBS=1 PKG_CONFIG_ALLOW_SYSTEM_CFLAGS=1 pkg-config --libs --cflags wayland-client

  The system library `wayland-client` required by crate `wayland-sys` was not found.
  The file `wayland-client.pc` needs to be installed and the PKG_CONFIG_PATH environment variable must contain its parent directory.
  The PKG_CONFIG_PATH environment variable is not set.

  HINT: if you have installed the library, try setting PKG_CONFIG_PATH to the directory containing `wayland-client.pc`.

If you've encountered an error message similar to the one above, this means that you will want to make the wayland-client library available to your build system, or disable default features, in order to successfully build Bevy on Linux.

On Ubuntu, or other Debian-based distributions, install the libwayland-dev package:

sudo apt install libwayland-dev

On Arch Linux:

sudo pacman -S wayland

On Nix, add the wayland package to your buildInputs:

buildInputs = [ pkgs.wayland ];

Generic Option Parameter #

PRs:#18766

Option<Single<D, F>> will now resolve to None if there are multiple entities matching the query. Previously, it would only resolve to None if there were no entities, and would skip the system if there were multiple.

We have introduced a blanket impl SystemParam for Option that resolves to None if the parameter is invalid. This allows third-party system parameters to work with Option, and makes the behavior more consistent.

If you want a system to run when there are no matching entities but skip when there are multiple, you will need to use Query<D, F> and call single() yourself.

// 0.16
fn my_system(single: Option<Single<&Player>>) {
}

// 0.17
fn my_system(query: Query<&Player>) {
    let result = query.single();
    if matches!(r, Err(QuerySingleError(MultipleEntities(_)))) {
        return;
    }
    let single: Option<&Player> = r.ok();
}

Stop exposing mp3 support through minimp3 #

PRs:#20183

The minimp3 feature is no longer exposed from Bevy. Bevy still supports mp3 through the mp3 feature.

If you were relying on something specific to minimp3, you can still enable it by adding a dependency to rodio with the minimp3 feature:

[dependencies]
rodio = { version = "0.20", features = ["minimp3"] }

This is best to avoid though, as minimp3 is not actively maintained, doesn't work in wasm, has been known to cause application rejection from the Apple App Store, and has a few security vulnerabilities.

Schedule API Cleanup #

In order to support removing systems from schedules, Vecs storing Systems and SystemSets have been replaced with SlotMaps which allow safely removing nodes and reusing indices. The maps are respectively keyed by SystemKeys and SystemSetKeys.

The following signatures were changed:

  • DiGraph and UnGraph now have an additional, required type parameter N, which is a GraphNodeId. Use DiGraph<NodeId>/UnGraph<NodeId> for the equivalent to the previous type.
  • NodeId::System: Now stores a SystemKey instead of a plain usize
  • NodeId::Set: Now stores a SystemSetKey instead of a plain usize
  • ScheduleBuildPass::collapse_set: Now takes the type-specific keys. Wrap them back into a NodeId if necessary.
  • ScheduleBuildPass::build: Now takes a DiGraph<SystemKey> instead of DiGraph<NodeId>. Re-wrap the keys back into NodeId if necessary.
  • The following functions now return the type-specific keys. Wrap them back into a NodeId if necessary.
    • Schedule::systems
    • ScheduleGraph::conflicting_systems
  • ScheduleBuildError variants now contain NodeId or type-specific keys, rather than Strings. Use ScheduleBuildError::to_string to render the nodes' names and get the old error messages.
  • ScheduleGraph::build_schedule now returns a Vec<ScheduleBuildWarning> in addition to the built SystemSchedule. Use standard Result functions to grab just the SystemSchedule, if needed.

The following functions were replaced. Those that took or returned NodeId now take or return SystemKey or SystemSetKey. Wrap/unwrap them as necessary.

  • ScheduleGraph::contains_set: Use ScheduleGraph::system_sets and SystemSets::contains.
  • ScheduleGraph::get_set_at: Use ScheduleGraph::system_sets and SystemSets::get.
  • ScheduleGraph::set_at: Use ScheduleGraph::system_sets and SystemSets::index (system_sets[key]).
  • ScheduleGraph::get_set_conditions_at: Use ScheduleGraph::system_sets and SystemSets::get_conditions.
  • ScheduleGraph::system_sets: Use ScheduleGraph::system_sets and SystemSets::iter.
  • ScheduleGraph::get_system_at: Use ScheduleGraph::systems and Systems::get.
  • ScheduleGraph::system_at: Use ScheduleGraph::systems and Systems::index (systems[key]).
  • ScheduleGraph::systems: Use ScheduleGraph::systems and Systems::iter.

The following enum variants were replaced:

  • ScheduleBuildError::HierarchyRedundancy with ScheduleBuildError::Elevated(ScheduleBuildWarning::HierarchyRedundancy)
  • ScheduleBuildError::Ambiguity with ScheduleBuildError::Elevated(ScheduleBuildWarning::Ambiguity)

The following functions were removed:

  • NodeId::index: You should match on and use the SystemKey and SystemSetKey instead.
  • NodeId::cmp: Use the PartialOrd and Ord traits instead.
  • ScheduleGraph::set_conditions_at: If needing to check presence of conditions, use ScheduleGraph::system_sets and SystemSets::has_conditions. Otherwise, use SystemSets::get_conditions.

bevy_render reorganization #

You must now import bevy_render::NormalizedRenderTargetExt to use methods on NormalizedRenderTarget ManualTextureViews is now in bevy_render::texture

Camera types such as Camera, Camera3d, Camera2d, ClearColor, ClearColorConfig, Exposure, Projection, PerspectiveProjection, and OrthographicProjection have been moved to a new crate, bevy_camera. Visibility types such as Visibility, InheritedVisibility, ViewVisibility, VisibleEntities, and RenderLayers have been moved to bevy_camera::visibility. Culling primitives such as Frustum, HalfSpace, Aabb, and Sphere have been moved to bevy_camera::primitives. Import them directly or from bevy::camera now.

Shader types such as Shader, ShaderRef, ShaderDef, ShaderCache, and PipelineCompilationError have been moved to a new crate, bevy_shader. Import them directly or from bevy::shader now.

Light types such AmbientLight, PointLight, SpotLight, DirectionalLight, EnvironmentMapLight, GeneratedEnvironmentMapLight, LightProbe, IrradianceVolume, VolumetricFog, FogVolume, CascadeShadowConfigBuilder, NotShadowCaster, NotShadowReceiver and light_consts have been moved to a new crate, bevy_light. Import them directly or from bevy::light now.

Mesh types such as Mesh, Mesh3d, Mesh2d, MorphWeights, MeshBuilder, Indices, and Meshable have been moved to a new crate, bevy_mesh. Import them directly or from bevy::mesh now. This crate is actually present in the previous release, but its bevy_render re-exports have now been removed.

Image types such as Image, ImagePlugin, ImageFormat, ImageSampler, ImageAddressMode, ImageSamplerDescriptor, ImageCompareFunction, and ImageSamplerBorderColor have been moved to a new crate, bevy_image. This crate is actually present in the previous release, but its bevy_render re-exports have now been removed. Import them directly or from bevy::image now.

Ui rendering types such as MaterialNode, UiMaterial, UiMaterialKey, and modules bevy_ui::render and bevy_ui::ui_material have been moved to a new crate, bevy_ui_render. Import them directly or from bevy::ui_render now. Furthermore, UiPlugin no longer has any fields. To control whether or not UI is rendered, enable or disable UiRenderPlugin, which is included in the DefaultPlugins. If you were manually enabling "bevy_ui" feature on bevy, you probably want to enable "bevy_ui_render" feature instead now if you are using rendering features.

Sprite rendering types such as Material2d, Material2dPlugin, MeshMaterial2d, AlphaMode2d, Wireframe2d, TileData, TilemapChunk, and TilemapChunkTileData have been moved to a new crate, bevy_sprite_render. Import them directly or from bevy::sprite_render now. If you were manually enabling "bevy_sprite" feature on bevy, you probably want to enable "bevy_sprite_render" feature instead now if you are using rendering features such as 2d gizmos.

RenderAssetUsages is no longer re-exported by bevy_render. Import it from bevy_asset or bevy::asset instead.

bevy_core_pipeline used to be home to many non-core things, including post process effects. They have now been given a new home in bevy_anti_alias and bevy_post_process.

If you were importing FxaaPlugin, SmaaPlugin, TemporalAntiAliasPlugin, or CasPlugin from bevy_core_pipeline or bevy::core_pipeline, you must now import them from bevy_anti_alias or bevy::anti_alias.

If you were importing Bloom, AutoExposure, ChromaticAberration, DepthOfField, or MotionBlur from bevy_core_pipeline or bevy::core_pipeline, you must now import them from bevy_post_process or bevy::post_process.

Additionally, you may now order rendering passes against the new StartMainPassPostProcessing node.

Rename Pointer<Pressed> and Pointer<Released> to Pointer<Press> and Pointer<Release> #

PRs:#19179

The Pointer<Pressed> and Pointer<Released> events have been renamed to Pointer<Press> and Pointer<Release> for improved consistency. Pressed is now a marker component indicating that a button or other UI node is in a pressed or "held down" state.

YAxisOrientation has been removed #

PRs:#19077

The YAxisOrientation component has been removed from bevy_text. The correct y-axis orientation is now chosen automatically by the text systems.

Renamed Timer::paused to Timer::is_paused and Timer::finished to Timer::is_finished #

PRs:#19386

The following changes were made:

  • Timer::paused is now Timer::is_paused
  • Timer::finished is now Timer::is_finished

This change was made to align the Timer public API with that of Time and Stopwatch.

glTF animation loading is now optional #

PRs:#20750

GltfLoaderSettings now has a load_animations field which allows controlling whether animations should load.

Fixed UI draw order and stack_z_offsets changes #

PRs:#19691

The draw order of some renderable UI elements relative to others wasn't fixed and depended on system ordering. In particular the ordering of background colors and texture sliced images was sometimes swapped.

The UI draw order is now fixed. The new order is (back-to-front):

  1. Box shadows
  2. Node background colors
  3. Node borders
  4. Gradients
  5. Border Gradients
  6. Images (including texture-sliced images)
  7. Materials
  8. Text (including text shadows)

The values of the stack_z_offsets constants have been updated to enforce the new ordering. Other changes:

  • NODE is renamed to BACKGROUND_COLOR
  • TEXTURE_SLICE is removed, use IMAGE.
  • New BORDER, BORDER_GRADIENT and TEXT constants.

RemovedComponents methods renamed to match Event to Message rename #

As part of the broader shift to differentiate between buffered events (0.16's EventWriter/EventReader) and observer-events, various methods and types related to RemovedComponents have been renamed.

The implementation of RemovedComponents uses buffered events (now, messages): and as a result, the following types and messages have been renamed:

OldNew
RemovedComponents::eventsRemovedComponents::messages
RemovedComponents::reader_mut_with_eventsRemovedComponents::reader_mut_with_messages
RemovedComponentEventsRemovedComponentMessages

Window is now split into multiple components #

PRs:#19668

Window has become a very large component over the last few releases. To improve our internal handling of it and to make it more approachable, we have split it into multiple components, all on the same entity. So far, this affects CursorOptions:

// old
fn lock_cursor(primary_window: Single<&mut Window, With<PrimaryWindow>>) {
    primary_window.cursor_options.grab_mode = CursorGrabMode::Locked;
}

// new
fn lock_cursor(primary_cursor_options: Single<&mut CursorOptions, With<PrimaryWindow>>) {
    primary_cursor_options.grab_mode = CursorGrabMode::Locked;
}

This split also applies when specifying the initial settings for the primary window:

// old
app.add_plugins(DefaultPlugins.set(WindowPlugin {
    primary_window: Some(Window {
        cursor_options: CursorOptions {
            grab_mode: CursorGrabMode::Locked,
            ..default()
        },
        ..default()
    }),
    ..default()
}));

// new
app.add_plugins(DefaultPlugins.set(WindowPlugin {
    primary_cursor_options: Some(CursorOptions {
        grab_mode: CursorGrabMode::Locked,
        ..default()
    }),
    ..default()
}));

Removed cosmic_text re-exports #

PRs:#19516

Previously, bevy_text re-exported the entirety of cosmic_text while renaming a few of the most confusing re-exports, using the following code.

pub use cosmic_text::{
    self, FamilyOwned as FontFamily, Stretch as FontStretch, Style as FontStyle, Weight as FontWeight,
};

These re-exports commonly conflicted with other types (like Query!), leading to messy autocomplete errors. Ultimately, these are largely an implementation detail, and were not widely used.

We've completely removed these re-exports (including the renamed types): if you need to use these types, please rely on them directly from cosmic_text, being sure that the version number you are using matches the version used by your version of bevy_text.

Rename send_event and similar methods to write_message #

Following up on the EventWriter::send being renamed to EventWriter::write in 0.16, many similar methods have been renamed. Note that "buffered events" are now known as Messages, and the naming reflects that here.

This includes both the World and Commands message methods. The old methods have been depreciated.

OldNew
World::send_eventWorld::write_message
World::send_event_defaultWorld::write_message_default
World::send_event_batchWorld::write_message_batch
DeferredWorld::send_eventDeferredWorld::write_message
DeferredWorld::send_event_defaultDeferredWorld::write_message_default
DeferredWorld::send_event_batchDeferredWorld::write_message_batch
Commands::send_eventCommands::write_message
Events::sendMessages::write
Events::send_defaultMessages::write_default
Events::send_batchMessages::write_batch
RemovedComponentEvents::sendRemovedComponentEvents::write
command::send_eventcommand::write_message
SendBatchIdsWriteBatchIds

Move UI Debug Options from bevy_ui to bevy_ui_render #

PRs:#18703

The UiDebugOptions resource used for controlling the UI Debug Overlay has been moved from the internal bevy_ui crate to the bevy_ui_render crate, and is now accessible from the prelude of bevy_ui_render and, as before, from the prelude of bevy:

// 0.16
use bevy::prelude::*;
// or
use bevy::ui::UiDebugOptions;

// 0.17
use bevy::prelude::*;
// or, if you are not using the full `bevy` crate:
// use bevy_ui_render::prelude::*;

let options = world.resource_mut::<UiDebugOptions>();

CloneBehavior is no longer PartialEq or Eq #

PRs:#18393

CloneBehavior no longer implements PartialEq or Eq and thus does not work with the == and != operators, as the internal comparisons involve comparing function pointers which may result in unexpected results.

Use pattern matching to check for equality instead:

// 0.16
if clone_behavior == CloneBehavior::Ignore {
   ...
}

// 0.17
if matches!(clone_behavior, CloneBehavior::Ignore) {
   ...
}

Interned labels and DynEq #

PRs:#18984
  • DynEq::as_any has been removed. Use &value as &dyn Any instead.
  • DynHash::as_dyn_eq has been removed. Use &value as &dyn DynEq instead.
  • as_dyn_eq has been removed from 'label' types such as ScheduleLabel and SystemSet. Call DynEq::dyn_eq directly on the label instead.

Split Hdr from Camera #

PRs:#18873

Camera.hdr has been split out into a new marker component, Hdr, which can be found at bevy::render::view::Hdr.

  • before: commands.spawn((Camera3d, Camera { hdr: true, ..default() });
  • after: commands.spawn((Camera3d, Hdr));
  • rendering effects can now #[require(Hdr)] if they only function with an HDR camera. This is currently implemented for Bloom, AutoExposure, and Atmosphere

The render target info from ComputedUiTargetCamera has been removed. #

PRs:#20535

The render target info, scale factor and physical size, has been removed from ComputedUiTargetCamera and placed into a new component ComputedUiRenderTargetInfo.

Reflection-based maps are now unordered #

PRs:#19802

DynamicMap is now unordered, and the Map trait no longer assumes implementors to be ordered. If you previously relied on them being ordered, you should now store a list of keys (Vec<Box<dyn PartialReflect>>) separately.

Map::get_at and Map::get_at_mut are now removed. You should no longer use usize to index into the map, and instead use &dyn PartialReflect with Map::get and Map::get_mut.

PartialReflect::apply(self, other) for maps now removes excess entries (entries present in self which are not present in other). If you need those entries to be preserved, you will need to re-insert them manually.

Rework MergeMeshError #

PRs:#18561

MergeMeshError was reworked to account for the possibility of the meshes being merged having two different PrimitiveTopology's, and was renamed to MeshMergeError to align with the naming of other mesh errors.

  • Users will need to rename MergeMeshError to MeshMergeError
  • When handling MergeMeshError (now MeshMergeError), users will need to account for the new IncompatiblePrimitiveTopology variant, as it has been changed from a struct to an enum
  • Mesh::merge now returns Result<(), MeshMergeError> instead of the previous Result<(), MergeMeshError>

Use glTF material names for spawned primitive entities #

PRs:#19287

When loading a glTF scene in Bevy, each mesh primitive will generate an entity and store a GltfMaterialName component and Name component.

The Name components were previously stored as mesh name plus primitive index - for example, MeshName.0 and MeshName.1. To make it easier to view these entities in Inspector-style tools, they are now stored as mesh name plus material name - for example, MeshName.Material1Name and MeshName.Material2Name.

If you were relying on the previous value of the Name component on meshes, use the new GltfMeshName component instead.

ViewRangefinder3d::from_world_from_view now takes Affine3A instead of Mat4 #

PRs:#20707

ViewRangefinder3d::from_world_from_view now takes Affine3A instead of Mat4. If you were supplying a GlobalTransform::to_matrix(), simply use GlobalTransform::affine() now. Performance will be better.

Original target of Pointer picking events is now stored on observers #

PRs:#19663

The Pointer.target field, which tracks the original target of the pointer event before bubbling, has been removed. Instead, all "bubbling entity event" observers now track this information, available via the On::original_entity() method.

If you were using this information via the Pointer API of picking, please migrate to observers. If you cannot for performance reasons, please open an issue explaining your exact use case!

As a workaround, you can transform any entity-event into a Message that contains the targeted entity using an observer than writes messages.

#[derive(Message)]
struct EntityEventMessage<E: EntityEvent> {
    entity: Entity,
    event: E,
}

// A generic observer that handles this transformation
fn transform_entity_event<E: EntityEvent>(event: On<E>, message_writer: MessageWriter<EntityEventMessage<E>>){
    if event.entity() == event.original_entity() {
        message_writer.send(EntityEventMessage {
            event: event.event().clone(),
            entity: event.entity(),
        );
    }
}

Remove the Add/Sub impls on Volume #

PRs:#19423

Linear volumes are like percentages, and it does not make sense to add or subtract percentages. As such, use the new increase_by_percentage function instead of addition or subtraction.

// 0.16
fn audio_system() {
    let linear_a = Volume::Linear(0.5);
    let linear_b = Volume::Linear(0.1);
    let linear_c = linear_a + linear_b;
    let linear_d = linear_a - linear_b;
}

// 0.17
fn audio_system() {
    let linear_a = Volume::Linear(0.5);
    let linear_b = Volume::Linear(0.1);
    let linear_c = linear_a.increase_by_percentage(10.0);
    let linear_d = linear_a.increase_by_percentage(-10.0);
}

Renamed ComputedNodeTarget and update_ui_context_system #

ComputedNodeTarget has been renamed to ComputedUiTargetCamera. New name chosen because the component's value is derived from UiTargetCamera.

update_ui_context_system has been renamed to propagate_ui_target_cameras.

ChromaticAberration LUT is now Option #

PRs:#19408

The ChromaticAberration component color_lut field use to be a regular Handle<Image>. Now, it is an Option<Handle<Image>> which falls back to the default image when None. For users assigning a custom LUT, just wrap the value in Some.

Extract PointerInputPlugin members into PointerInputSettings #

PRs:#19078

Toggling mouse and touch input update for picking should be done through the PointerInputSettings resource instead of PointerInputPlugin.

To initialize PointerInputSettings with non-default values, simply add the resource to the app using insert_resource with the desired value.

DragEnter now includes the dragged entity #

PRs:#19179

DragEnter events are now triggered when entering any entity, even the originally dragged one. This makes the behavior more consistent.

The old behavior can be achieved by checking if trigger.entity != trigger.dragged

Separate Border Colors #

PRs:#18682

The BorderColor struct now contains separate fields for each edge, top, bottom, left, right. To keep the existing behavior, replace BorderColor(color) with BorderColor::all(color), and border_color.0 = new_color with *border_color = BorderColor::all(new_color).

ScrollPosition now uses logical pixel units and is no longer overwritten during layout updates #

PRs:#20093

ScrollPosition is no longer overwritten during layout updates. Instead the computed scroll position is stored in the new scroll_position field on ComputedNode.

Combine now takes an extra parameter #

PRs:#20689

The Combine::combine method now takes an extra parameter that needs to be passed mutably to the two given closures. This allows fixing a soundness issue which manifested when the two closures were called re-entrantly.

Fix From<Rot2> implementation for Mat2 #

PRs:#20522

Past releases had an incorrect From<Rot2> implementation for Mat2, constructing a rotation matrix in the following form:

[  cos, sin ]
[ -sin, cos ]

This was actually the inverse of the rotation matrix, resulting in clockwise rotation when transforming vectors. The correct version is the following:

[ cos, -sin ]
[ sin,  cos ]

resulting in counterclockwise rotation.

This error has now been fixed. You may see that rotation matrices created using the From<Rot2> implementation produce different results than before, as the rotation happens counterclockwise (as intended) rather than clockwise. Invert either the input Rot2 or the resulting Mat2 to get the same results as before.

Extract PickingPlugin members into PickingSettings #

PRs:#19078

Controlling the behavior of picking should be done through the PickingSettings resource instead of PickingPlugin.

To initialize PickingSettings with non-default values, simply add the resource to the app using insert_resource with the desired value.

Consistent *Systems naming convention for system sets #

PRs:#18900

System sets in Bevy now more consistently use a Systems suffix. Renamed types include:

  • AccessibilitySystemAccessibilitySystems
  • GizmoRenderSystemGizmoRenderSystems
  • PickSetPickingSystems
  • RunFixedMainLoopSystemRunFixedMainLoopSystems
  • TransformSystemTransformSystems
  • RemoteSetRemoteSystems
  • RenderSetRenderSystems
  • SpriteSystemSpriteSystems
  • StateTransitionStepsStateTransitionSystems
  • RenderUiSystemRenderUiSystems
  • UiSystemUiSystems
  • AnimationAnimationSystems
  • AssetEventsAssetEventSystems
  • TrackAssetsAssetTrackingSystems
  • UpdateGizmoMeshesGizmoMeshSystems
  • InputSystemInputSystems
  • InputFocusSetInputFocusSystems
  • ExtractMaterialsSetMaterialExtractionSystems
  • ExtractMeshesSetMeshExtractionSystems
  • RumbleSystemRumbleSystems
  • CameraUpdateSystemCameraUpdateSystems
  • ExtractAssetsSetAssetExtractionSystems
  • Update2dTextText2dUpdateSystems
  • TimeSystemTimeSystems
  • EventUpdatesEventUpdateSystems

To improve consistency within the ecosystem, it is recommended for ecosystem crates and users to also adopt the *Systems naming convention for their system sets where applicable.

System::run returns Result #

PRs:#19145

In order to support fallible systems and parameter-based system skipping like Single and If<T> in more places, System::run and related methods now return a Result instead of a plain value.

If you were calling System::run, System::run_unsafe, System::run_without_applying_deferred, or ReadOnlySystem::run_readonly, the simplest solution is to unwrap() the resulting Result. The only case where an infallible system will return Err is an invalid parameter, such as a missing resource, and those cases used to panic.

If you were calling them from a function that returns Result<T, BevyError>, you can instead use the ? operator.

System::run, System::run_without_applying_deferred, and ReadOnlySystem::run_readonly will now call System::validate_param_unsafe and return Err if validation fails. If you were calling validate_param or validate_param_unsafe before calling one of those, it is no longer necessary. Note that System::run_unsafe still does not perform validation.

If you were manually implementing System, the return type to run_unsafe has changed from Out to Result<Out, RunSystemError>. If you are implementing an infallible system, simply wrap the return value in Ok. If you were implementing a fallible system and had set type Out = Result<T, BevyError>;, instead set type Out = T;.

If you have a system function that returns Result or ! and are not restricting the return type, you may get type inference failures like this:

error[E0283]: type annotations needed
    --> lib.rs:100:5
     |
100  |     IntoSystem::into_system(example_system);
     |     ^^^^^^^^^^^^^^^^^^^^^^^ cannot infer type of the type parameter `Out` declared on the trait `IntoSystem`
     |
note: multiple `impl`s satisfying `core::result::Result<(), bevy_error::BevyError>: function_system::IntoResult<_>` found
   --> crates\bevy_ecs\src\system\function_system.rs:597:1

or

error[E0283]: type annotations needed
    --> lib.rs:100:11
    |
100 |     world.run_system_cached(system).unwrap();
    |           ^^^^^^^^^^^^^^^^^ cannot infer type of the type parameter `O` declared on the method `run_system_cached`
    |
note: multiple `impl`s satisfying `core::result::Result<(), bevy_error::BevyError>: function_system::IntoResult<_>` found
   --> crates\bevy_ecs\src\system\function_system.rs:597:1

A function that returns Result<T, BevyError> may be considered either a fallible system that returns T or an infallible system that returns Result, and a function that returns ! may be considered a system that returns any type. You should be able to resolve them by providing an explicit type for System::Out.

fn example_system() -> Result { Ok(()) }
// 0.16 - Output type is inferred to be `Result`
IntoSystem::into_system(example_system)
// 0.17 - Output type can be either `()` or `Result` and must be written explicitly
IntoSystem::<_, (), _>::into_system(example_system);
IntoSystem::<_, Result, _>::into_system(example_system);

// 0.16 - Output type is inferred to be `Result`
world.run_system_cached(example_system).unwrap().unwrap();
// 0.17 - Output type can be either `()` or `Result` and must be written explicitly
world.run_system_cached::<(), _, _>(example_system).unwrap();
world.run_system_cached::<Result, _, _>(example_system).unwrap().unwrap();
// or it may be inferred if the output type is specified elsewhere
let _: () = world.run_system_cached(example_system).unwrap();
let r: Result = world.run_system_cached(example_system).unwrap();

LightVisibilityClass renamed to ClusterVisibilityClass #

PRs:#19986

When clustered decals were added, they used LightVisibilityClass to share the clustering infrastructure. This revealed that this visibility class wasn't really about lights, but about clustering. It has been renamed to ClusterVisibilityClass and moved to live alongside clustering-specific types.

Renamed state scoped entities and events #

Previously, Bevy provided the StateScoped component and add_state_scoped_event method as a way to remove entities/events when exiting a state.

However, it can also be useful to have the opposite behavior, where entities/events are removed when entering a state. This is now possible with the new DespawnOnEnter component and clear_events_on_enter method.

To support this addition, the previous method and component have been renamed. Also, clear_event_on_exit (previously clear_event_on_exit_state) no longer adds the event automatically, so you must call App::add_event manually.

BeforeAfter
StateScopedDespawnOnExit
clear_state_scoped_entitiesdespawn_entities_on_exit_state
add_state_scoped_eventadd_event + clear_events_on_exit

Renamed JustifyText to Justify #

PRs:#19522

JustifyText has been renamed to Justify.

The -Text suffix was inconsistent with the names of other bevy_text types and unnecessary since it's natural to assume Justify refers to text justification.

Remove default implementation of extend_from_iter from RelationshipSourceCollection #

PRs:#20255

The extend_from_iter method in the RelationshipSourceCollection trait no longer has a default implementation. If you have implemented a custom relationship source collection, you must now provide your own implementation of this method.

// Before: method was optional due to default implementation
impl RelationshipSourceCollection for MyCustomCollection {
    // ... other required methods
    // extend_from_iter was automatically provided
}

// After: method is now required
impl RelationshipSourceCollection for MyCustomCollection {
    // ... other required methods
    fn extend_from_iter(&mut self, entities: impl IntoIterator<Item = Entity>) {
        // Use your collection's native extend method if available
        self.extend(entities);
        // Or implement manually if needed:
        // for entity in entities {
        //     self.add(entity);
        // }
    }
}

AnimationGraph no longer supports raw AssetIds #

PRs:#19615

In previous versions of Bevy, AnimationGraph would serialize Handle<AnimationClip> as an asset path, and if that wasn't available it would fallback to serializing AssetId<AnimationClip>. In practice, this was not very useful. AssetId is (usually) a runtime-generated ID. This means for an arbitrary Handle<AnimationClip>, it was incredibly unlikely that your handle before serialization would correspond to the same asset as after serialization.

This confusing behavior has been removed. As a side-effect, any AnimationGraphs you previously saved (via AnimationGraph::save) will need to be re-saved. These legacy AnimationGraphs can still be loaded until the next Bevy version. Loading and then saving the AnimationGraph again will automatically migrate the AnimationGraph.

If your AnimationGraph contained serialized AssetIds, you will need to manually load the bytes of the saved graph, deserialize it into SerializedAnimationGraph, and then manually decide how to migrate those AssetIds. Alternatively, you could simply rebuild the graph from scratch and save a new instance. We expect this to be a very rare situation.

Unify ObserverState and Observer components #

PRs:#18728

ObserverState and Observer have been merged into a single component. now you can use Observer::with_dynamic_runner to build custom Observe.

let observe = unsafe {
    Observer::with_dynamic_runner(|world, trigger_context, event_ptr, trigger_ptr| {
        // do something
    })
    .with_event(event_a)
};
world.spawn(observe);

take_extract now returns dyn FnMut instead of dyn Fn #

PRs:#19926

Previously, set_extract accepted any Fn. Now we accept any FnMut. For callers of set_extract, there is no difference since Fn: FnMut.

However, callers of take_extract will now be returned Option<Box<dyn FnMut(&mut World, &mut World) + Send>> instead.

OpenGL ES wgpu backend is no longer supported by default #

PRs:#20793

The gles backend for wgpu is no longer included as a default feature of bevy_render. OpenGL support is still available, but must be explicitly enabled by adding the bevy_render/gles feature to your app. This change reflects the fact that OpenGL support is not tested and that some features may not work as expected or at all. We welcome contributions to improve OpenGL support in the future.

labeled_asset_scope can now return errors #

PRs:#19449

labeled_asset_scope now returns a user-specified error type based on their closure. Previously, users would need to fall back to begin_labeled_asset and add_loaded_labeled_asset to handle errors, which is more error-prone. Consider migrating to use labeled_asset_scope if this was you!

However, labeled_asset_scope closures that don't return errors now needs to A) return Ok, and B) specify an error type.

If your code previously looked like this:

labeled_asset_scope(label, |mut load_context| {
  let my_asset = ...;

  my_asset
});

You can migrate it to:

labeled_asset_scope::<_, ()>(label, |mut load_context| {
  let my_asset = ...;

  Ok(my_asset)
}).unwrap();

Make ScrollPosition newtype Vec2 #

PRs:#19881

ScrollPosition now newtypes Vec2; its offset_x and offset_y fields have been removed.

PRs:#20427

In an effort to reduce and untangle dependencies, cursor-related types have been moved from the bevy_winit crate to the bevy_window crate. The following types have been moved as part of this change:

  • CursorIcon is now located at bevy::window::CursorIcon.
  • CustomCursor is now located at bevy::window::CustomCursor.
  • CustomCursorImage is now located at bevy::window::CustomCursorImage.
  • CustomCursorUrl is now located at bevy::window::CustomCursorUrl.
  • on the android platform, ANDROID_APP is now located in it's own crate and can be found at bevy::android::ANDROID_APP.

ExtractedUiNode's stack_index has been renamed to z_order and is now an f32. #

PRs:#19691

ExtractedUiNode’s stack_index field has been renamed to z_order and its type changed from u32 to f32. Previously stack_index would be converted into an f32 after extraction during the Render schedule, then offsets would be applied applied to determine draw order before sorting (lowest value rendered first). For example, a node's fill color is given an offset of 0. and a box shadow is given an offset of -0.1, so that the shadow will be drawn behind the node.

Changing the field to an f32, enables finer control of the UI draw order by allowing these offsets to be applied during extraction, and fixes a bug affecting the ordering of texture-sliced nodes.

Changes to Bevy's system parallelism strategy #

PRs:#16885

The scheduler will now prevent systems from running in parallel if there could be an archetype that they conflict on, even if there aren't actually any. To expand on this, previously, the scheduler would look at the entities that exist to determine if there's any overlap. Now, it determines it solely on the basis of the function signatures of the systems that are run. This was done as a performance optimization: while in theory this throws away potential parallelism, in practice, tests on engine and user code found that scheduling overhead dominates parallelism gains with the current multithreaded executor. Swapping to this simpler, more conservative test allows us to speed up these checks and improve resource utilization. There are more improvements planned here, so stay tuned!

To understand what this means, consider the following example. These systems will now conflict even if no entity has both Player and Enemy components:

fn player_system(query: Query<(&mut Transform, &Player)>) {}
fn enemy_system(query: Query<(&mut Transform, &Enemy)>) {}

To allow them to run in parallel, use Without filters, just as you would to allow both queries in a single system:

// Either one of these changes alone would be enough
fn player_system(query: Query<(&mut Transform, &Player), Without<Enemy>>) {}
fn enemy_system(query: Query<(&mut Transform, &Enemy), Without<Player>>) {}

If you encounter a performance regression in your application due to this change, please open an issue. We would be very interested to understand your use case, see your benchmarking results, and help you mitigate any issues.

wgpu 25 #

PRs:#19563

wgpu 25 introduces a number of breaking changes, most notably in the way Bevy is required to handle uniforms with dynamic offsets which are used pervasively in the renderer. Dynamic offsets and uniforms of any kind are no longer allowed to be used in the same bind group as binding arrays. As such, the following changes to the default bind group numbering have been made in 3d:

  • @group(0) view binding resources
  • @group(1) view resources requiring binding arrays
  • @group(2) mesh binding resources
  • @group(3) material binding resources

Most users who are not using mid-level render APIs will simply need to switch their material bind groups from @group(2) to @group(#{MATERIAL_BIND_GROUP}). The MATERIAL_BIND_GROUP shader def has been added to ensure backwards compatibility in the event the bind group numbering changes again in the future.

Exported float constants from shaders without an explicit type declaration like const FOO = 1.0; are no longer supported and must be explicitly typed like const FOO: f32 = 1.0;.

See the full changelog here.

When migrating shaders or other custom rendering code, you may encounter panics like:

wgpu error: Validation Error

Caused by:
  In Device::create_render_pipeline, label = 'pbr_opaque_mesh_pipeline'
    Error matching ShaderStages(FRAGMENT) shader requirements against the pipeline
      Shader global ResourceBinding { group: 2, binding: 100 } is not available in the pipeline layout
        Binding is missing from the pipeline layout

note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
Encountered a panic in system `bevy_render::render_resource::pipeline_cache::PipelineCache::process_pipeline_queue_system`!

This error is a result of Bevy's bind group indices changing. Identify the shader by searching for the group and binding mentioned, e.g. @group(2) @binding(100), and follow the above advice to fix the binding group index.

Anchor is now a required component on Sprite #

PRs:#18393

The anchor field has been removed from Sprite. Instead the Anchor component is now a required component on Sprite.

The anchor variants have been moved to associated constants, following the table below:

0.160.17
Anchor::CenterAnchor::Center
Anchor::BottomLeftAnchor::BOTTOM_LEFT
Anchor::BottomCenterAnchor::BOTTOM_CENTER
Anchor::BottomRightAnchor::BOTTOM_RIGHT
Anchor::CenterLeftAnchor::CENTER_LEFT
Anchor::CenterRightAnchor::CENTER_RIGHT
Anchor::TopLeftAnchor::TOP_LEFT
Anchor::TopCenterAnchor::TOP_CENTER
Anchor::TopRightAnchor::TOP_RIGHT
Anchor::Custom(value)Anchor(value)

Many render resources now initialized in RenderStartup #

Many render resources are no longer present during Plugin::finish. Instead they are initialized during RenderStartup (which occurs once the app starts running). If you only access the resource during the Render schedule, then there should be no change. However, if you need one of these render resources to initialize your own resource, you will need to convert your resource initialization into a system.

The following are the (public) resources that are now initialized in RenderStartup.

  • CasPipeline
  • FxaaPipeline
  • SmaaPipelines
  • TaaPipeline
  • ShadowSamplers
  • GlobalClusterableObjectMeta
  • FallbackBindlessResources
  • AutoExposurePipeline
  • MotionBlurPipeline
  • SkyboxPrepassPipeline
  • BlitPipeline
  • DepthOfFieldGlobalBindGroupLayout
  • DepthPyramidDummyTexture
  • OitBuffers
  • PostProcessingPipeline
  • TonemappingPipeline
  • BoxShadowPipeline
  • GradientPipeline
  • UiPipeline
  • UiMaterialPipeline<M>
  • UiTextureSlicePipeline
  • ScreenshotToScreenPipeline
  • VolumetricFogPipeline
  • DeferredLightingLayout
  • CopyDeferredLightingIdPipeline
  • RenderLightmaps
  • PrepassPipeline
  • PrepassViewBindGroup
  • Wireframe3dPipeline
  • ScreenSpaceReflectionsPipeline
  • MaterialPipeline
  • MeshletPipelines
  • MeshletMeshManager
  • ResourceManager
  • Wireframe2dPipeline
  • Material2dPipeline
  • SpritePipeline
  • Mesh2dPipeline
  • BatchedInstanceBuffer<Mesh2dUniform>

The vast majority of cases for initializing render resources look like so (in Bevy 0.16):

impl Plugin for MyRenderingPlugin {
    fn build(&self, app: &mut App) {
        // Do nothing??
    }

    fn finish(&self, app: &mut App) {
        let Some(render_app) = app.get_sub_app_mut(RenderApp) else {
            return;
        };

        render_app.init_resource::<MyRenderResource>();
        render_app.add_systems(Render, my_render_system);
    }
}

pub struct MyRenderResource {
    ...
}

impl FromWorld for MyRenderResource {
    fn from_world(world: &mut World) -> Self {
        let render_device = world.resource::<RenderDevice>();
        let render_adapter = world.resource::<RenderAdapter>();
        let asset_server = world.resource::<AssetServer>();

        MyRenderResource {
            ...
        }
    }
}

The two main things to focus on are:

  1. The resource implements the FromWorld trait which collects all its dependent resources (most commonly, RenderDevice), and then creates an instance of the resource.
  2. The plugin adds its systems and resources in Plugin::finish.

First, we need to rewrite our FromWorld implementation as a system. This generally means converting calls to World::resource into system params, and then using Commands to insert the resource. In the above case, that would look like:

// Just a regular old system!!
fn init_my_resource(
    mut commands: Commands,
    render_device: Res<RenderDevice>,
    render_adapter: Res<RenderAdapter>,
    asset_server: Res<AssetServer>,
) {
    commands.insert_resource(MyRenderResource {
        ...
    });
}

Each case will be slightly different. Two notes to be wary of:

  1. Functions that accept &RenderDevice for example may no longer compile after switching to Res<RenderDevice>. This can be resolved by passing &render_device instead of render_device.
  2. If you are using load_embedded_asset(world, "my_asset.png"), you may need to first add asset_server as a system param, then change this to load_embedded_asset(asset_server.as_ref(), "my_asset.png").

Now that we have our initialization system, we just need to add the system to RenderStartup:

impl Plugin for MyRenderingPlugin {
    fn build(&self, app: &mut App) {
        let Some(render_app) = app.get_sub_app_mut(RenderApp) else {
            return;
        };

        render_app
            .add_systems(RenderStartup, init_my_resource)
            .add_systems(Render, my_render_system);
    }

    // No more finish!!
}

In addition, if your resource requires one of the affected systems above, you will need to use system ordering to ensure your resource initializes after the other system. For example, if your system uses Res<UiPipeline>, you will need to add an ordering like:

render_app.add_systems(RenderStartup, init_my_resource.after(init_ui_pipeline));

GatedReader and GatedOpener are now private. #

PRs:#18473

The GatedReader and GatedOpener for bevy_asset have been made private. These were really only for testing, but were being compiled even in release builds. Now they are guarded by #[cfg(test)]!

If you were using this in your own tests, you could fork the GatedReader (it still exists in the Bevy repo!) into your own code, or write your own version (if more useful to you).

Updated glam, rand and getrandom versions with new failures when building for web #

PRs:#18047

We've upgraded glam and the other math crates (encase, hexasphere) that move in lockstep to the latest versions. With newer versions of glam & encase, the updated versions don't seem to have introduced breakages, though as always, best to consult their docs 1 2 for any changes.

This has also upgraded the version of rand and getrandom that Bevy relies on. rand changes are more extensive, with changes such as thread_rng() -> rng(), from_entropy() -> from_os_rng(), and so forth. RngCore is now split into infallible RngCore and fallible TryRngCore, and the distributions module has been renamed to distr. Most of this affects only internals, and doesn't directly affect Bevy's APIs. For the full set of changes, see rand migration notes.

getrandom is also updated, and will require additional configuration when building Bevy for WASM/web browsers. This will affect you even if you are not using rand or getrandom directly, as glam (and thus bevy_math) will pull it in.

You may encounter an error like:

error: the wasm*-unknown-unknown targets are not supported by default;
to enable support, add this to your `Cargo.toml`:

[dependencies]
getrandom = { version = "0.3", features = ["wasm_js"] }

This is due to a breaking change in how getrandom handles entropy generation. For security reasons, this is no longer specified via feature flags, as any crate in your dependency tree could quietly enable additional entropy sources.

Quoting from the getrandom docs on WebAssembly support in getrandom 2:

To enable getrandom's functionality on wasm32-unknown-unknown using the Web Crypto methods described above via wasm-bindgen, do both of the following:

  1. Use the wasm_js feature flag, i.e. getrandom = { version = "0.3", features = ["wasm_js"] }. On its own, this only makes the backend available. (As a side effect this will make your Cargo.lock significantly larger if you are not already using wasm-bindgen, but otherwise enabling this feature is harmless.)
  2. Set RUSTFLAGS='--cfg getrandom_backend="wasm_js"' (see above).

Note that if you were previously setting the RUSTFLAGS environment variable for any reason, this will override any previous settings: you need to add this to your existing list instead.

If you were using the community-provided Bevy CLI to easily create builds of your game for different platforms (including web), make sure to update to v0.1.0-alpha.2 or later, which will automatically configure RUSTFLAGS for you.

OverflowClipBox's default is now PaddingBox #

PRs:#18935

The default variant for OverflowClipBox is now PaddingBox. The default value for OverflowClipMargin::visual_box is now OverflowClipBox::PaddingBox.

Event trait split / Rename #

PRs:#19647

"Buffered events" (things sent/read using EventWriter / EventReader) are now no longer referred to as "events", in the interest of conceptual clarity and learn-ability (see the release notes for rationale). "Event" as a concept (and the Event trait) are now used solely for "observable events". "Buffered events" are now known as "messages" and use the Message trait. EventWriter, EventReader, and Events<E>, are now known as MessageWriter, MessageReader, and Messages<M>. Types can be both "messages" and "events" by deriving both Message and Event, but we expect most types to only be used in one context or the other.

Deprecated Simple Executor #

PRs:#18753

Bevy has deprecated SimpleExecutor, one of the SystemExecutors in Bevy alongside SingleThreadedExecutor and MultiThreadedExecutor (which aren't going anywhere any time soon).

The SimpleExecutor leaves performance on the table compared to the other executors in favor of simplicity. Specifically, SimpleExecutor applies any commands a system produces right after it finishes, so every system starts with a clean World with no pending commands. As a result, the default SimpleExecutor runs all systems in the order they are added to the schedule, though more ordering constraints can be applied, like before, after, chain, etc. In other executors, these ordering onstraints also inform the executor exactly where to apply commands. For example, if system A produces commands and runs before system B, A's commands will be applied before B starts. However, the before ordering is implicit in SimpleExecutor if A is added to the schedule before B.

The dueling behavior between ordering systems based on when they were added to a schedule as opposed to using ordering constraints is difficult to maintain and can be confusing, especially for new users. But, if you have a strong preference for the existing behavior of SimpleExecutor, please make an issue and we can discuss your needs.

If you were using SimpleExecutor, consider upgrading to SingleThreadedExecutor instead, or try MultiThreadedExecutor if it fits the schedule. The MultiThreadedExecutor is great at large schedules and async heavy work, and the SingleThreadedExecutor is good at smaller schedules or schedules that have fewer parallelizable systems. So what was SimpleExecutor good at? Not much. That's why we plan to remove it. Removing it will reduce some maintenance and consistency burdens, allowing us to focus on more exciting features!

When migrating, you might uncover bugs where one system depends on another's commands but is not ordered to reflect that. These bugs can be fixed by making those implicit orderings explicit via constraints like before, after, chain, etc. If finding all of those implicit but necessary orderings is unrealistic, chain can also be used to mimic the behavior of the SimpleExecutor. Again, if you run into trouble migrating, feel free to open an issue!

ReflectAsset now uses UntypedAssetId instead of UntypedHandle #

PRs:#19606

Previously, ReflectAsset methods all required having UntypedHandle. The only way to get an UntypedHandle through this API was with ReflectAsset::add. ReflectAsset::ids was not very useful in this regard.

Now, all methods have been changed to accept impl Into<UntypedAssetId>, which matches our regular Assets<T> API. This means you may need to change how you are calling these methods.

For example, if your code previously looked like:

let my_handle: UntypedHandle;
let my_asset = reflect_asset.get_mut(world, my_handle).unwrap();

You can migrate it to:

let my_handle: UntypedHandle;
let my_asset = reflect_asset.get_mut(world, &my_handle).unwrap();

Replaced TextFont constructor methods with From impls #

The TextFont::from_font and TextFont::from_line_height constructor methods have been removed in favor of From trait implementations.

// 0.16
let text_font = TextFont::from_font(font_handle);
let text_font = TextFont::from_line_height(line_height);

// 0.17
let text_font = TextFont::from(font_handle);
let text_font = TextFont::from(line_height);

VectorSpace implementations #

PRs:#19194

Previously, implementing VectorSpace for a type required your type to use or at least interface with f32. This made implementing VectorSpace for double-precision types (like DVec3) less meaningful and useful, requiring lots of casting. VectorSpace has a new required associated type Scalar that's bounded by a new trait ScalarField. bevy_math implements this trait for f64 and f32 out of the box, and VectorSpace is now implemented for DVec[N] types.

If you manually implemented VectorSpace for any type, you'll need to implement Scalar for it. If you were working with single-precision floating-point types and you want the exact behavior from before, set it to f32.

Improve error when using run_system command with a SystemId of wrong type #

PRs:#19011

Added a new error to RegisteredSystemError to inform of use of run_system command and its variants with a SystemId of wrong type.

Extract UI text colors per glyph #

PRs:#20245

The UI renderer now extracts text colors per glyph and transforms per text section. color: LinearRgba and translation: Vec2 have been added to ExtractedGlyph. The transform field has moved from ExtractedGlyph and ExtractedUiNode to ExtractedUiItem. The rect field has moved from ExtractedUiNode to ExtractedUiItem.

TextureFormat::pixel_size now returns a Result #

PRs:#20574

The TextureFormat::pixel_size() method now returns a Result<usize, TextureAccessError> instead of usize.

This change was made because not all texture formats have a well-defined pixel size (e.g. compressed formats). Previously, calling this method on such formats could lead to runtime panics. The new return type makes the API safer and more explicit about this possibility.

To migrate your code, you will need to handle the Result returned by pixel_size().

Entities API changes #

Entities::flush now also asks for metadata about the flush operation that will be stored for the flushed entities. For the source location, MaybeLocation::caller() can be used; the tick should be retrieved from the world.

Additionally, flush now gives &mut EntityIdLocation instead of &mut EntityLocation access. EntityIdLocation is an alias for Option<EntityLocation>. This replaces invalid locations with None. It is possible for an Entity id to be allocated/reserved but not yet have a location. This is used in commands for example, and this reality is more transparent with an Option. This extends to other interfaces: Entities::free now returns Option<EntityIdLocation> instead of Option<EntityLocation>. Entities::get remains unchanged, but you can access an Entity's EntityIdLocation through the new Entities::get_id_location.

Relationship method set_risky #

PRs:#19601

The trait Relationship received a new method, set_risky. It is used to alter the entity ID of the entity that contains its RelationshipTarget counterpart. This is needed to leave other data you can store in these components unchanged at operations that reassign the relationship target, for example EntityCommands::add_related. Previously this could have caused the data to be reset to its default value which may not be what you wanted to happen.

Manually overwriting the component is still possible everywhere the full component is inserted:

#[derive(Component)]
#[relationship(relationship_target = CarOwner)]
struct OwnedCar {
    #[relationship]
    owner: Entity,
    first_owner: Option<Entity>, // None if `owner` is the first one
}

#[derive(Component)]
#[relationship_target(relationship = OwnedCar)]
struct CarOwner(Vec<Entity>);

let mut me_entity_mut = world.entity_mut(me_entity);

// if `car_entity` already contains `OwnedCar`, then the first owner remains unchanged
me_entity_mut.add_one_related::<OwnedCar>(car_entity);

// if `car_entity` already contains `OwnedCar`, then the first owner is overwritten with None here
car_entity_mut.insert(OwnedCar {
    owner: me_entity,
    first_owner: None // I swear it is not stolen officer!
});

The new method should not be called by user code as that can invalidate the relationship it had or will have.

If you implement Relationship manually (which is strongly discouraged) then this method needs to overwrite the Entity used for the relationship.

Unified system state flag #

PRs:#19506

Now the system have a unified SystemStateFlags to represent its different states.

If your code previously looked like this:

impl System for MyCustomSystem {
    // ...

    fn is_send(&self) -> bool {
        false
    }

    fn is_exclusive(&self) -> bool {
        true
    }

    fn has_deferred(&self) -> bool {
        false
    }

    // ....
}

You should migrate it to:

impl System for MyCustomSystem{
  // ...

  fn flags(&self) -> SystemStateFlags {
    // non-send , exclusive , no deferred
    SystemStateFlags::NON_SEND | SystemStateFlags::EXCLUSIVE
  }

  // ...
}

DynamicBundle #

In order to reduce the stack size taken up by spawning and inserting large bundles, the way the (mostly internal) trait DynamicBundle gets called has changed significantly:

// 0.16
trait DynamicBundle {
    type Effect;
    
    // hidden in the docs
    fn get_components(self, func: &mut impl FnMut(StorageType, OwningPtr<'_>)) -> Self::Effect;
}

// 0.17
trait DynamicBundle {
    type Effect;
    
    unsafe fn get_components(ptr: MovingPtr<'_, Self>, func: &mut impl FnMut(StorageType, MovingPtr<'_>));
    unsafe fn apply_effect(ptr: MovingPtr<'_, MaybeUninit<Self>>, entity: &mut EntityWorldMut);
}

To prevent unnecessary copies to the stack, get_components now takes a MovingPtr<'_, Self> instead of self by value.

MovingPtr<T> is a safe, typed, box-like pointer that owns the data it points to, but not the underlying memory: that means the owner of a MovingPtr<T> can freely move parts of the data out and doesn't have to worry about de-allocating memory. Much like Box<T>, MovingPtr<T> implements Deref and DerefMut for easy access to the stored type, when it's safe to do so. To decompose the value inside of the MovingPtr<T> into its fields without copying them to the stack, you can use the deconstruct_moving_ptr! macro to give you MovingPtr<U>s to each field specified:

struct MySpecialBundle<A: Bundle, B: Bundle> {
    a: A,
    b: B,
}
let my_ptr: MovingPtr<'_, MySpecialBundle<u32, String>> = ...;
deconstruct_moving_ptr!(my_ptr => { a, b, });
let a_ptr: MovingPtr<'_, u32> = a;
let b_ptr: MovingPtr<'_, String> = b;

Similar to Box::into_inner, MovingPtr<T> also has a method MovingPtr::read for moving the whole value out of the pointer onto the stack:

let a: u32 = a_ptr.read();
let b: String = b_ptr.read();

apply_effect is a new method that takes the job of the old BundleEffect trait, and gets called once after get_components for any B::Effect: !NoBundleEffect. Since get_components might have already partially moved out some of the fields of the bundle, apply_effect takes a MovingPtr<'_, MaybeUninit<Self>> and implementers must make sure not to create any references to fields that are no longer initialized. Likewise, implementers of get_components must take care not to move out fields that will be needed in apply_effect. deconstruct_moving_ptr! can be used to selectively move out fields while ensuring the rest are forgotten, and remain valid for the subsequent call to apply_effect. The associated type Effect remains as a vestigial marker to keep track of whether apply_effect needs to be called for any B::Effect: !NoBundleEffect.

Exclusive systems may not be used as observers #

PRs:#19033

Exclusive systems may no longer be used as observers. This was never sound, as the engine keeps references alive during observer invocation that would be invalidated by &mut World access, but was accidentally allowed. Instead of &mut World, use either DeferredWorld if you do not need structural changes, or Commands if you do.

EntityClonerBuilder Split #

EntityClonerBuilder is now generic and has different methods depending on the generic.

To get the wanted one, EntityCloner::build got split too:

  • EntityCloner::build_opt_out to get EntityClonerBuilder<OptOut>
  • EntityCloner::build_opt_in to get EntityClonerBuilder<OptIn>

The first is used to clone all components possible and optionally opting out of some. The second is used to only clone components as specified by opting in for them.

// 0.16
let mut builder = EntityCloner.build(&mut world);
builder.allow_all().deny::<ComponentThatShouldNotBeCloned>();
builder.clone_entity(source_entity, target_entity);

let mut builder = EntityCloner.build(&mut world);
builder.deny_all().allow::<ComponentThatShouldBeCloned>();
builder.clone_entity(source_entity, target_entity);

// 0.17
let mut builder = EntityCloner.build_opt_out(&mut world);
builder.deny::<ComponentThatShouldNotBeCloned>();
builder.clone_entity(source_entity, target_entity);

let mut builder = EntityCloner.build_opt_in(&mut world);
builder.allow::<ComponentThatShouldBeCloned>();
builder.clone_entity(source_entity, target_entity);

Still, using EntityClonerBuilder::finish will return a non-generic EntityCloner. This change is done because the behavior of the two is too different to share the same struct and same methods and mixing them caused bugs.

The methods of the two builder types are different to 0.16 and to each other now:

Opt-Out variant #

  • Still offers variants of the deny methods.
  • No longer offers allow methods, you need to be exact with denying components.
  • Offers now the insert_mode method to configure if components are cloned if they already exist at the target.
  • Required components of denied components are no longer considered. Denying A, which requires B, does not imply B alone would not be useful at the target. So if you do not want to clone B too, you need to deny it explicitly. This also means there is no without_required_components method anymore as that would be redundant.
  • It is now the other way around: Denying A, which is required by C, will now also deny C. This can be bypassed with the new without_required_by_components method.

Opt-In variant #

  • Still offers variants of the allow methods.
  • No longer offers deny methods, you need to be exact with allowing components.
  • Offers now allow_if_new method variants that only clone this component if the target does not contain it. If it does, required components of it will also not be cloned, except those that are also required by one that is actually cloned.
  • Still offers the without_required_components method.

Common methods #

All other methods EntityClonerBuilder had in 0.16 are still available for both variants:

  • with_default_clone_fn
  • move_components
  • clone_behavior variants
  • linked_cloning

Unified id filtering #

Previously EntityClonerBuilder supported filtering by 2 types of ids: ComponentId and TypeId, the functions taking in IntoIterator for them. Since now EntityClonerBuilder supports filtering by BundleId as well, the number of method variations would become a bit too unwieldy. Instead, all id filtering methods were unified into generic deny_by_ids/allow_by_ids(_if_new) methods, which allow to filter components by TypeId, ComponentId, BundleId and their IntoIterator variations.

Other affected APIs #

0.160.17
EntityWorldMut::clone_withEntityWorldMut::clone_with_opt_out EntityWorldMut::clone_with_opt_in
EntityWorldMut::clone_and_spawn_withEntityWorldMut::clone_and_spawn_with_opt_out EntityWorldMut::clone_and_spawn_with_opt_in
EntityCommands::clone_withEntityCommands::clone_with_opt_out EntityCommands::clone_with_opt_in
EntityCommands::clone_and_spawn_withEntityCommands::clone_and_spawn_with_opt_out EntityCommands::clone_and_spawn_with_opt_in
entity_command::clone_withentity_command::clone_with_opt_out entity_command::clone_with_opt_in

Changes to type registration for reflection #

Calling .register_type has long been a nuisance for Bevy users: both library authors and end users. This step was previously required in order to register reflected type information in the TypeRegistry.

In Bevy 0.17 however, types which implement Reflect are now automatically registered, with the help of some compiler magic. You should be able to remove almost all of your register_type calls. This comes with a few caveats however:

  1. Automatic type registration is gated by feature flags.
  2. There are two approaches to do this: one has incomplete platform support, while the other relies on a specific project structure.
  3. Generic types are not automatically registered, and must still be manually registered.

In order for Bevy to automatically register your types, you need to turn on the reflect_auto_register feature, or the fallback reflect_auto_register_static. The reflect_auto_register feature is part of Bevy's default features, and can be overridden by the reflect_auto_register_static feature flag. Be aware that the reflect_auto_register_static feature comes with some caveats for project structure: check the docs for load_type_registrations! and follow the auto_register_static example.

We recommend that you:

  1. Enable reflect_auto_register in your application code, CI and in examples/tests. You can enable bevy features for tests only by adding a matching copy of bevy to dev-dependencies with the needed features enabled.
  2. Do not enable the reflect_auto_register feature or the fallback reflect_auto_register_static in your library code.
  3. As a library author, you can safely remove all non-generic .register_type calls.
  4. As a user, if you run into an unregistered generic type with the correct feature enabled, file a bug with the project that defined the offending type, and workaround it by calling .register_type manually.
  5. If you are on an unsupported platform but need reflection support, try the reflect_autoregister_static feature, and consider working upstream to add support for your platform in inventory. As a last resort, you can still manually register all of the needed types in your application code.

Composable Specialization #

PRs:#17373

The existing pipeline specialization APIs (SpecializedRenderPipeline etc.) have been replaced with a single Specializer trait and Variants collection:

pub trait Specializer<T: Specializable>: Send + Sync + 'static {
    type Key: SpecializerKey;
    fn specialize(
        &self,
        key: Self::Key,
        descriptor: &mut T::Descriptor,
    ) -> Result<Canonical<Self::Key>, BevyError>;
}

pub struct Variants<T: Specializable, S: Specializer<T>>{ ... };

For more info on specialization, see the docs for bevy_render::render_resources::Specializer

Mutation and Base Descriptors #

The main difference between the old and new trait is that instead of producing a pipeline descriptor, Specializers mutate existing descriptors based on a key. As such, Variants::new takes in a "base descriptor" to act as the template from which the specializer creates pipeline variants.

When migrating, the "static" parts of the pipeline (that don't depend on the key) should become part of the base descriptor, while the specializer itself should only change the parts demanded by the key. In the full example below, instead of creating the entire pipeline descriptor the specializer only changes the msaa sample count and the bind group layout.

Composing Specializers #

Specializers can also be composed with the included derive macro to combine their effects! This is a great way to encapsulate and reuse specialization logic, though the rest of this guide will focus on migrating "standalone" specializers.

pub struct MsaaSpecializer {...}
impl Specialize<RenderPipeline> for MsaaSpecializer {...}

pub struct MeshLayoutSpecializer {...}
impl Specialize<RenderPipeline> for MeshLayoutSpecializer {...}

#[derive(Specializer)]
#[specialize(RenderPipeline)]
pub struct MySpecializer {
    msaa: MsaaSpecializer,
    mesh_layout: MeshLayoutSpecializer,
}

Misc Changes #

The analogue of SpecializedRenderPipelines, Variants, is no longer a Bevy Resource. Instead, the cache should be stored in a user-created Resource (shown below) or even in a Component depending on the use case.

Full Migration Example #

Before:

#[derive(Resource)]
pub struct MyPipeline {
    layout: BindGroupLayout,
    layout_msaa: BindGroupLayout,
    vertex: Handle<Shader>,
    fragment: Handle<Shader>,
}

// before
#[derive(Clone, Copy, PartialEq, Eq, Hash)]
pub struct MyPipelineKey {
    msaa: Msaa,
}

impl FromWorld for MyPipeline {
    fn from_world(world: &mut World) -> Self {
        let render_device = world.resource::<RenderDevice>();
        let asset_server = world.resource::<AssetServer>();

        let layout = render_device.create_bind_group_layout(...);
        let layout_msaa = render_device.create_bind_group_layout(...);

        let vertex = asset_server.load("vertex.wgsl");
        let fragment = asset_server.load("fragment.wgsl");
        
        Self {
            layout,
            layout_msaa,
            vertex,
            fragment,
        }
    }
}

impl SpecializedRenderPipeline for MyPipeline {
    type Key = MyPipelineKey;

    fn specialize(&self, key: Self::Key) -> RenderPipelineDescriptor {
        RenderPipelineDescriptor {
            label: Some("my_pipeline".into()),
            layout: vec![
                if key.msaa.samples() > 1 {
                    self.layout_msaa.clone()
                } else { 
                    self.layout.clone() 
                }
            ],
            vertex: VertexState {
                shader: self.vertex.clone(),
                ..default()
            },
            multisample: MultisampleState {
                count: key.msaa.samples(),
                ..default()
            },
            fragment: Some(FragmentState {
                shader: self.fragment.clone(),
                targets: vec![Some(ColorTargetState {
                    format: TextureFormat::Rgba8Unorm,
                    blend: None,
                    write_mask: ColorWrites::all(),
                })],
                ..default()
            }),
            ..default()
        },
    }
}

render_app
    .init_resource::<MyPipeline>();
    .init_resource::<SpecializedRenderPipelines<MySpecializer>>();

After:

#[derive(Resource)]
pub struct MyPipeline {
    // the base_descriptor and specializer each hold onto the static
    // wgpu resources (layout, shader handles), so we don't need
    // explicit fields for them here. However, real-world cases
    // may still need to expose them as fields to create bind groups
    // from, for example.
    variants: Variants<RenderPipeline, MySpecializer>,
}

pub struct MySpecializer {
    layout: BindGroupLayout,
    layout_msaa: BindGroupLayout,
}

#[derive(Clone, Copy, PartialEq, Eq, Hash, SpecializerKey)]
pub struct MyPipelineKey {
    msaa: Msaa,
}

impl FromWorld for MyPipeline {
    fn from_world(world: &mut World) -> Self {
        let render_device = world.resource::<RenderDevice>();
        let asset_server = world.resource::<AssetServer>();

        let layout = render_device.create_bind_group_layout(...);
        let layout_msaa = render_device.create_bind_group_layout(...);

        let vertex = asset_server.load("vertex.wgsl");
        let fragment = asset_server.load("fragment.wgsl");

        let base_descriptor = RenderPipelineDescriptor {
            label: Some("my_pipeline".into()),
            vertex: VertexState {
                shader: vertex.clone(),
                ..default()
            },
            fragment: Some(FragmentState {
                shader: fragment.clone(),
                ..default()
            }),
            ..default()
        },

        let variants = Variants::new(
            MySpecializer {
                layout: layout.clone(),
                layout_msaa: layout_msaa.clone(),
            },
            base_descriptor,
        );
        
        Self { variants }
    }
}

impl Specializer<RenderPipeline> for MySpecializer {
    type Key = MyKey;

    fn specialize(
        &self,
        key: Self::Key,
        descriptor: &mut RenderPipeline,
    ) -> Result<Canonical<Self::Key>, BevyError> {
        descriptor.multisample.count = key.msaa.samples();

        let layout = if key.msaa.samples() > 1 { 
            self.layout_msaa.clone()
        } else {
            self.layout.clone()
        };

        descriptor.set_layout(0, layout);

        Ok(key)
    }
}

render_app.init_resource::<MyPipeline>();

Entry enum is now ComponentEntry #

PRs:#19517

The Entry enum in bevy::ecs::world has been renamed to ComponentEntry, to avoid name clashes with hash_map, hash_table and hash_set Entry types.

Correspondingly, the nested OccupiedEntry and VacantEntry enums have been renamed to OccupiedComponentEntry and VacantComponentEntry.

State-scoped entities are now always enabled implicitly #

State scoped entities is now always enabled, and as a consequence, app.enable_state_scoped_entities::<State>() is no longer needed. It has been marked as deprecated and does nothing when called.

The attribute #[states(scoped_entities)] has been removed. You can safely remove it from your code without replacement.

Required components refactor #

PRs:#20110

The required components feature has been reworked to be more consistent around the priority of the required components and fix some soundness issues. In particular:

  • the priority of required components will now always follow a priority given by the depth-first/preorder traversal of the dependency tree. This was mostly the case before with a couple of exceptions that we are now fixing:
    • when deriving the Component trait, sometimes required components at depth 1 had priority over components at depth 2 even if they came after in the depth-first ordering;
    • registering runtime required components followed a breadth-first ordering and used the wrong inheritance depth for derived required components.
  • uses of the inheritance depth were removed from the RequiredComponent struct and from the methods for registering runtime required components, as it's not unused for the depth-first ordering;
  • Component::register_required_components, RequiredComponents::register and RequiredComponents::register_by_id are now unsafe;
  • RequiredComponentConstructor's only field is now private for safety reasons.

The Component::register_required_components method has also changed signature. It now takes the ComponentId of the component currently being registered and a single other parameter RequiredComponentsRegistrator which combines the old components and required_components parameters, since exposing both of them was unsound. As previously discussed the inheritance_depth is now useless and has been removed, while the recursion_check_stack has been moved into ComponentsRegistrator and will be handled automatically.

view_transformations.wgsl deprecated in favor of view.wgsl #

PRs:#20313

All functions in view_transformations.wgsl have been replaced and deprecated.

To migrate, a straight-forward copy-paste inlining of the deprecated function's new body suffices, as they all now call the new api internally.

For example, if you had before:

#import bevy_pbr::view_transformations

let world_pos = view_transformations::position_view_to_world(view_pos);

Now it would be:

#import bevy_render::view

let world_pos = view::position_view_to_world(view_pos, view_bindings::view.world_from_view);

This was done to make it possible to pass in custom view bindings, and allow code reuse.

view_transformations.wgsl will be deleted in 0.18.

Replace Gilrs, AccessKitAdapters, and WinitWindows non-send resources #

We are working to move !Send data out of the ECS, in order to simplify internal implementation, reduce the risk of soundness problems and unblock features such as resources-as-entities and improved scheduling.

For now, the API for user-provided NonSend types is unchanged, but we are considering forcing all users to migrate to a solution similar to the one discussed below.

First-party NonSend Resources Replaced #

Internally, we have replaced the following resources:

  • Gilrs - For wasm32 only, other platforms are unchanged - Replaced with bevy_gilrs::GILRS
  • WinitWindows - Replaced with bevy_winit::WINIT_WINDOWS
  • AccessKitAdapters - Replaced with bevy_winit::ACCESS_KIT_ADAPTERS

Each of these are now using thread_locals to store the data and are temporary solutions to storing !Send data. Even though thread_locals are thread safe, they should not be accessed from other threads. If they are accessed from other threads, the data will be uninitialized in each non-main thread, which isn't very useful.

Here is an example of how the data can now be accessed. This example will use WINIT_WINDOWS as an example, but the same technique can be applied to the others:

Immutable Access #

use bevy_winit::WINIT_WINDOWS;

...

WINIT_WINDOWS.with_borrow(|winit_windows| {
    // do things with `winit_windows`
});

Mutable Access #

use bevy_winit::WINIT_WINDOWS;

...

WINIT_WINDOWS.with_borrow_mut(|winit_windows| {
    // do things with `winit_windows`
});

If a borrow is attempted while the data is borrowed elsewhere, the method will panic.

NonSend Systems #

The use of a NonSend or NonSendMut resource in a system would force the system to execute on the main thread. However, when using the new thread_local pattern, we still need to prevent systems from running on non-main threads. To do this, you can now use bevy_ecs::system::NonSendMarker as a system parameter:

use bevy_ecs::system::NonSendMarker;

fn my_system(
    _non_send_marker: NonSendMarker,
) {
    ACCESS_KIT_ADAPTERS.with_borrow_mut(|adapters| {
        // do things with adapters
    });
}

To prevent a panic, if any of the !Send resource replacements mentioned in this document are used in a system, the system should always be marked as !Send with bevy_ecs::system::NonSendMarker.

ComponentsRegistrator no longer implements DerefMut #

ComponentsRegistrator no longer implements DerefMut<Target = Components>, meaning you won't be able to get a &mut Components from it. The only two methods on Components that took &mut self (any_queued_mut and num_queued_mut) have been reimplemented on ComponentsRegistrator, meaning you won't need to migrate them. Other usages of &mut Components were unsupported.

Query items can borrow from query state #

The QueryData::Item associated type and the QueryItem and ROQueryItem type aliases now have an additional lifetime parameter corresponding to the 's lifetime in Query. The QueryData::fetch() and QueryFilter::filter_fetch() methods have a new parameter taking a &'s WorldQuery::State. Manual implementations of WorldQuery and QueryData will need to update the method signatures to include the new lifetimes. Other uses of the types will need to be updated to include a lifetime parameter, although it can usually be passed as '_. In particular, ROQueryItem is used when implementing RenderCommand.

Before:

// 0.16
fn render<'w>(
    item: &P,
    view: ROQueryItem<'w, Self::ViewQuery>,
    entity: Option<ROQueryItem<'w, Self::ItemQuery>>,
    param: SystemParamItem<'w, '_, Self::Param>,
    pass: &mut TrackedRenderPass<'w>,
) -> RenderCommandResult;

// 0.17
fn render<'w>(
    item: &P,
    view: ROQueryItem<'w, '_, Self::ViewQuery>,
    entity: Option<ROQueryItem<'w, '_, Self::ItemQuery>>,
    param: SystemParamItem<'w, '_, Self::Param>,
    pass: &mut TrackedRenderPass<'w>,
) -> RenderCommandResult;

Methods on QueryState that take &mut self may now result in conflicting borrows if the query items capture the lifetime of the mutable reference. This affects get(), iter(), and others. To fix the errors, first call QueryState::update_archetypes(), and then replace a call state.foo(world, param) with state.query_manual(world).foo_inner(param). Alternately, you may be able to restructure the code to call state.query(world) once and then make multiple calls using the Query.

let mut state: QueryState<_, _> = ...;

// 0.16
let d1 = state.get(world, e1);
let d2 = state.get(world, e2); // Error: cannot borrow `state` as mutable more than once at a time

println!("{d1:?}");
println!("{d2:?}");

// 0.17
state.update_archetypes(world);
let d1 = state.get_manual(world, e1);
let d2 = state.get_manual(world, e2);
// OR
state.update_archetypes(world);
let d1 = state.query_manual(world).get_inner(e1);
let d2 = state.query_manual(world).get_inner(e2);
// OR
let query = state.query(world);
let d1 = query.get_inner(e1);
let d1 = query.get_inner(e2);

println!("{d1:?}");
println!("{d2:?}");

Non-generic Access #

PRs:#20288

Now that archetype_component_id has been removed, Access, AccessFilters, FilteredAccess, and FilteredAccessSet were only ever parameterized by ComponentId. To simplify use of those types, the generic parameter has been removed. Remove the <Component> generic from any use of those types.

// 0.16
fn update_component_access(state: &Self::State, access: &mut FilteredAccess<ComponentId>) {}
// 0.17
fn update_component_access(state: &Self::State, access: &mut FilteredAccess) {}

Component lifecycle reorganization #

PRs:#19543

To improve documentation, discoverability and internal organization, we've gathered all of the component lifecycle-related code we could and moved it into a dedicated lifecycle module.

The lifecycle / observer types (Add, Insert, Remove, Replace, Despawn) have been moved from the bevy_ecs::world to bevy_ecs::lifecycle.

The same move has been done for the more internal (but public) ComponentId constants: ADD, INSERT, REMOVE, REPLACE, DESPAWN.

The code for hooks (HookContext, ComponentHook, ComponentHooks) has been extracted from the very long bevy_ecs::components module, and now lives in the bevy_ecs::lifecycle module.

The RemovedComponents SystemParam, along with the public RemovedIter, RemovedIterWithId and RemovedComponentEvents have also been moved into this module as they serve a similar role. All references to bevy_ecs::removal_detection can be replaced with bevy_ecs::lifecycle.

Polylines and Polygons are no longer const-generic #

PRs:#20250

Polyline2d, Polyline3d, Polygon, and ConvexPolygon are no longer const-generic and now implement Meshable for direct mesh generation. These types now use Vec instead of arrays internally and will therefore allocate and are no longer no_std compatible.

If you need these types to be no_std and/or const-generic, please file an issue explaining your use case and we can consider creating fixed side-count polygon/polyline variants.

Changes to the default error handler mechanism #

PRs:#18810

We've improved the implementation of Bevy's default error handling. The performance overhead has been reduced, and as a result it is always enabled. The configurable_error_handler feature no longer exists: simply remove it from your list of enabled features.

Additionally, worlds can now have different default error handlers, so there is no longer a truly global handler.

Replace uses of GLOBAL_ERROR_HANDLER with App::set_error_handler(handler). For worlds that do not directly belong to an App/SubApp, insert the DefaultErrorHandler(handler) resource.

Removed Deprecated Batch Spawning Methods #

PRs:#18148

The following deprecated functions have been removed:

  • Commands::insert_or_spawn_batch
  • World::insert_or_spawn_batch
  • World::insert_or_spawn_batch_with_caller

These functions, when used incorrectly, could cause major performance problems and generally violated the privacy of the ECS internals in ways that the Bevy maintainers were not prepared to support long-term. They were deprecated in 0.16 due to their potential for misuse, as the retained render world removed Bevy's own uses of these methods.

Instead of allocating entities with specific identifiers, consider one of the following:

  1. Instead of despawning entities, insert the Disabled component, and instead of respawning them at particular ids, use try_insert_batch or insert_batch and remove Disabled.

  2. Instead of giving special meaning to an entity id, simply use spawn_batch and ensure entity references are valid when despawning.

  3. Use your own stable identifier and a map to Entity identifiers, with the help of the EntityMapper trait.

Stop storing access in systems #

Bevy used to store component access in all systems, even though it was only used for top-level systems in schedules. To reduce memory usage, the component access is now stored in the schedule instead.

The trait methods System::component_access and System::component_access_set have been removed. Instead, the access is returned from System::initialize. If you were implementing System manually, the initialize method should return the access instead of storing it. If you were calling component_access or component_access_set on a system that you initialized yourself, you will need to store the access yourself.

let system = IntoSystem::into_system(your_system);
// 0.16
system.initialize(&mut world);
let access = system.component_access();
// 0.17
let component_access_set = system.initialize(&mut world);
let access = component_access_set.combined_access();

SystemMeta no longer stores FilteredAccessSet<ComponentId>. It is instead passed as a separate parameter when initializing a SystemParam.

To better share logic between SystemParam and SystemParamBuilder, SystemParam::init_state has been split into init_state, which creates the state value, and init_access, which calculates the access. SystemParamBuilder::build now only creates the state, and SystemParam::init_access will be called to calculate the access for built parameters.

If you were implementing SystemParam manually, you will need to separate the logic into two methods and change any uses of system_meta.component_access_set(_mut) to the new component_access_set parameter. Note that init_state no longer has access to SystemMeta or component_access_set, and init_access only has &state, so the state can no longer depend on the system.

If you were calling init_state manually, you will need to call init_access afterwards.

// 0.16
let param_state = P::init_state(world, &mut meta);
// 0.17
let param_state = P::init_state(world);
let mut component_access_set = FilteredAccessSet::new();
P::init_access(&param_state, &mut meta, &mut component_access_set, world);

New zstd backend #

PRs:#19793

A more performant zstd backend has been added for texture decompression. To enable it, disable default-features and enable feature "zstd_c". If you have default-features disabled and use functionality that requires zstd decompression ("tonemapping_luts" or "ktx2"), you must choose a zstd implementation with one of the following feature flags: "zstd_c" (faster) or "zstd_rust" (safer)

Migration Guide #

If you previously used the zstd feature explicitly, it has been renamed to zstd_rust:

# 0.16
[dependencies]
bevy = { version = "0.16", features = ["zstd"] }

# 0.17 - Use the safe Rust implementation:
[dependencies]
bevy = { version = "0.17", features = ["zstd_rust"] }

# 0.17 - Or use the faster C implementation:
[dependencies]
bevy = { version = "0.17", features = ["zstd_c"] }

If you have default-features disabled and use functionality that requires zstd decompression ("tonemapping_luts" or "ktx2"), you must choose a zstd implementation with one of the following feature flags: "zstd_c" (faster) or "zstd_rust" (safer).

Remove ArchetypeComponentId #

PRs:#19143

Scheduling no longer uses archetype_component_access or ArchetypeComponentId. To reduce memory usage and simplify the implementation, all uses of them have been removed. Since we no longer need to update access before a system runs, Query now updates it state when the system runs instead of ahead of time.

SystemParam::validate_param now takes &mut Self::State instead of &Self::State so that queries can update their state during validation.

The trait methods System::update_archetype_component_access and SystemParam::new_archetype have been removed. They are no longer necessary, so calls to them can be removed. If you were implementing the traits manually, move any logic from those methods into System::validate_param_unsafe, System::run_unsafe, SystemParam::validate_param, or SystemParam::get_param, which can no longer rely on update_archetype_component_access being called first.

The following methods on SystemState have been deprecated:

  • update_archetypes - Remove calls, as they no longer do anything
  • update_archetypes_unsafe_world_cell - Remove calls, as they no longer do anything
  • get_manual - Replace with get, as there is no longer a difference
  • get_manual_mut - Replace with get_mut, as there is no longer a difference
  • get_unchecked_mut - Replace with get_unchecked, as there is no longer a difference

RenderTarget error handling #

PRs:#20503

NormalizedRenderTargetExt::get_render_target_info now returns a Result, with the Err variant indicating which render target (image, window, etc) failed to load its metadata.

This should mostly be treated as a hard error, since it indicates the rendering state of the app is broken.

Renamed BRP methods #

PRs:#19377

Most Bevy Remote Protocol methods have been renamed to be more explicit. The word destroy has also been replaced with despawn to match the rest of the engine.

OldNew
bevy/queryworld.query
bevy/spawnworld.spawn_entity
bevy/destroyworld.despawn_entity
bevy/reparentworld.reparent_entities
bevy/getworld.get_components
bevy/insertworld.insert_components
bevy/removeworld.remove_components
bevy/listworld.list_components
bevy/mutateworld.mutate_components
bevy/get+watchworld.get_components+watch
bevy/list+watchworld.list_components+watch
bevy/get_resourceworld.get_resources
bevy/insert_resourceworld.insert_resources
bevy/remove_resourceworld.remove_resources
bevy/list_resourcesworld.list_resources
bevy/mutate_resourceworld.mutate_resources
registry/schemaregistry.schema

TAA is no longer experimental #

PRs:#18349

TAA is no longer experimental.

TemporalAntiAliasPlugin no longer needs to be added to your app to use TAA. It is now part of DefaultPlugins, via AntiAliasPlugin.

As part of this change, the import paths for TemporalAntiAliasNode, TemporalAntiAliasing and TemporalAntiAliasPlugin have changed from bevy::anti_alias::experimental::taa to bevy::anti_alias::taa: if you want to add TemporalAntiAliasing to a Camera, you can now find it at bevy::anti_alias::taa::TemporalAntiAliasing.

TemporalAntiAliasing now uses MipBias as a required component in the main world, instead of overriding it manually in the render world.

TextShadow has been moved to bevy::ui::widget::text #

TextShadow has been moved to bevy::ui::widget::text.

Remove Bundle::register_required_components #

PRs:#19967

This method was effectively dead-code as it was never used by the ECS to compute required components, hence it was removed. if you were overriding its implementation you can just remove it, as it never did anything. If you were using it in any other way, please open an issue.

Observer / Event API Changes #

The observer "trigger" API has changed a bit to improve clarity and type-safety.

// Old
commands.add_observer(|trigger: Trigger<OnAdd, Player>| {
    info!("Spawned player {}", trigger.target());
});

// New
commands.add_observer(|add: On<Add, Player>| {
    info!("Spawned player {}", add.entity);
});

The Trigger type used inside observers has been renamed to On to encourage developers to think about this parameter as the event. We also recommend naming the variable after the event type (ex: add).

To reduce repetition and improve readability, the OnAdd, OnInsert, OnReplace, OnRemove, and OnDespawn observer events have also been renamed to Add, Insert, Replace, Remove, and Despawn respectively. In rare cases where the Add event conflicts with the std::ops::Add trait, you may need to disambiguate, for example by using ops::Add for the trait. We encourage removing the "On" from custom events named OnX.

Types implementing Event can no longer be triggered from _all contexts. By default Event is a "global" / "target-less" event.

Events that target an entity should now derive EntityEvent, and they will now store the target entity on the event type, which is accessible via EntityEvent::event_target. Additionally, world.trigger_targets has been removed in favor of a single world.trigger API:

// Old
#[derive(Event)]
struct Explode;

world.trigger_targets(Explode, entity);

// New
#[derive(EntityEvent)]
struct Explode {
    entity: Entity
}

world.trigger(Explode { entity });

Triggering an entity event for multiple entities now requires multiple calls to trigger:

// Old
world.trigger_targets(Explode, [e1, e2]);

// New - Variant 1
world.trigger(Explode { entity: e1 });
world.trigger(Explode { entity: e2 });

// New - Variant 2
for entity in [e1, e2] {
    world.trigger(Explode { entity });
}

On::target() no longer exists for all event types. Instead, you should prefer accessing the "target entity" field on the events that target entities:

// Old
commands.add_observer(|trigger: Trigger<Explode>| {
    info!("{} exploded!", trigger.target());
});

// New
commands.add_observer(|explode: On<Explode>| {
    info!("{} exploded!", explode.entity);
    // you can also use `EntityEvent::event_target`, but we encourage
    // using direct field access when possible, for better documentation and clarity.
    info!("{} exploded!", explode.event_target());
});

"Propagation functions", such as On::propagate are now only available on On<E> when E: EntityEvent<Trigger = PropagateEntityTrigger>.

Enabling propagation is now down using, which defaults to ChildOf propagation:

#[derive(EntityEvent)]
#[entity_event(propagate)]
struct Click {
    entity: Entity,
}

Setting a custom propagation Traversal implementation now uses propagate instead of traversal:

// OLd
#[derive(Event)]
#[event(traversal = &'static ChildOf)]
struct Click;

// New
#[derive(EntityEvent)]
#[entity_event(propagate = &'static ChildOf)]
struct Click {
    entity: Entity,
}

Animation events (used in AnimationPlayer) must now derive AnimationEvent. Accessing the animation player entity is now done via the trigger().

// Old
#[derive(Event)]
struct SayMessage(String);

animation.add_event(0.2, SayMessage("hello".to_string()));
world.entity_mut(animation_player).observe(|trigger: Trigger<SayMessage>| {
    println!("played on", trigger.target());
})

// New
#[derive(AnimationEvent)]
struct SayMessage(String);

animation.add_event(0.2, SayMessage("hello".to_string()));
world.entity_mut(animation_player).observe(|say_message: On<SayMessage>| {
    println!("played on", say_message.trigger().animation_player);
})

For "component lifecycle events", accessing all of the components that triggered the event has changed:

// Old
commands.add_observer(|trigger: Trigger<OnAdd, Player>| {
    info!("{}", trigger.components());
});

// New
commands.add_observer(|add: On<Add, Player>| {
    info!("{}", add.trigger().components);
});

Manual Entity Creation and Representation #

An entity is made of two parts: and index and a generation. Both have changes:

Index #

Entity no longer stores its index as a plain u32 but as the new EntityRow, which wraps a NonMaxU32. Previously, Entity::index could be u32::MAX, but that is no longer a valid index. As a result, Entity::from_raw now takes EntityRow as a parameter instead of u32. EntityRow can be constructed via EntityRow::new, which takes a NonMaxU32. If you don't want to add nonmax as a dependency, use Entity::from_raw_u32 which is identical to the previous Entity::from_raw, except that it now returns Option where the result is None if u32::MAX is passed.

Bevy made this change because it puts a niche in the EntityRow type which makes Option<EntityRow> half the size of Option<u32>. This is used internally to open up performance improvements to the ECS.

Although you probably shouldn't be making entities manually, it is sometimes useful to do so for tests. To migrate tests, use:

- let entity = Entity::from_raw(1);
+ let entity = Entity::from_raw_u32(1).unwrap();

If you are creating entities manually in production, don't do that! Use Entities::alloc instead. But if you must create one manually, either reuse a EntityRow you know to be valid by using Entity::from_raw and Entity::row, or handle the error case of None returning from Entity::from_raw_u32(my_index).

Generation #

An entity's generation is no longer a NonZeroU32. Instead, it is an EntityGeneration. Internally, this stores a u32, but that might change later.

Working with the generation directly has never been recommended, but it is sometimes useful to do so in tests. To create a generation do EntityGeneration::FIRST.after_versions(expected_generation). To use this in tests, do assert_eq!(entity.generation(), EntityGeneration::FIRST.after_versions(expected_generation)).

Removed Interfaces #

The identifier module and all its contents have been removed. These features have been slimmed down and rolled into Entity.

This means that where Result<T, IdentifierError> was returned, Option<T> is now returned.

Functionality #

It is well documented that both the bit format, serialization, and Ord implementations for Entity are subject to change between versions. Those have all changed in this version.

For entity ordering, the order still prioritizes an entity's generation, but after that, it now considers higher index entities less than lower index entities.

The changes to serialization and the bit format are directly related. Effectively, this means that all serialized and transmuted entities will not work as expected and may crash. To migrate, invert the lower 32 bits of the 64 representation of the entity, and subtract 1 from the upper bits. Again, this is still subject to change, and serialized scenes may break between versions.

Length Representation #

Because the maximum index of an entity is now NonZeroU32::MAX, the maximum number of entities (and length of unique entity row collections) is u32::MAX. As a result, a lot of APIs that returned usize have been changed to u32.

These include:

  • Archetype::len
  • Table::entity_count

Other kinds of entity rows #

Since the EntityRow is a NonMaxU32, TableRow and ArchetypeRow have been given the same treatment. They now wrap a NonMaxU32, allowing more performance optimizations.

Additionally, they have been given new, standardized interfaces:

  • fn new(NonMaxU32)
  • fn index(self) -> usize
  • fn index_u32(self) -> u32

The other interfaces for these types have been removed. Although it's not usually recommended to be creating these types manually, if you run into any issues migrating here, please open an issue. If all else fails, TableRow and ArchetypeRow are repr(transparent), allowing careful transmutations.

RenderTargetInfo's default scale_factor has been changed to 1. #

PRs:#21802

The default for RenderTargetInfo's scale_factor field is now 1..

RenderGraphApp renamed to RenderGraphExt #

PRs:#19912

RenderGraphApp has been renamed to RenderGraphExt. Rename this for cases where you are explicitly importing this trait.

Window Resolution Constructors #

PRs:#20582

The WindowResolution type stores the width and height as u32. Previously, this type could only be constructed with f32, which were immediately converted to u32. Now, WindowResolution can be constructed with u32s directly, and the pointless f32 conversion has been removed.

WindowResolution::new(1920.0, 1080.0)
// becomes
WindowResolution::new(1920, 1080)

WindowResolution::new(some_uvec2.x as f32, some_uvec2.y as f32)
// becomes
WindowResolution::from(some_uvec2)

window_resolution: (1920.0, 1080.0).into()
// becomes
window_resolution: (1920, 1080).into()

Specialized UI transform #

PRs:#16615

Bevy UI now uses specialized 2D UI transform components UiTransform and UiGlobalTransform in place of Transform and GlobalTransform.

UiTransform is a 2D-only equivalent of Transform with a responsive translation in Vals. UiGlobalTransform newtypes Affine2 and is updated in ui_layout_system.

Node now requires UiTransform instead of Transform. UiTransform requires UiGlobalTransform.

The UiTransform equivalent of the Transform:

Transform {
    translation: Vec3 { x, y, z },
    rotation:Quat::from_rotation_z(radians),
    scale,
}

is

UiTransform {
    translation: Val2::px(x, y),
    rotation: Rot2::from_rotation(radians),
    scale: scale.xy(),
}

In previous versions of Bevy ui_layout_system would overwrite UI node's Transform::translation each frame. UiTransforms aren't overwritten and there is no longer any need for systems that cache and rewrite the transform for translated UI elements.

If you were relying on the z value of the GlobalTransform, this can be derived from UiStack instead.

CheckChangeTicks parameter in System::check_change_tick #

System::check_change_tick took a Tick parameter to update internal ticks. This is needed to keep queried components filtered by their change tick reliably not be matched if their last change or add and the system's last run was very long ago. This is also needed for similar methods involving the system's ticks for the same reason.

This parameter is now a CheckChangeTicks type that can be passed to the now-public Tick::check_tick in case you maintain these yourself in manual System implementations.

If you need a CheckChangeTicks value, for example because you call one of the above methods manually, you can observe it. Here is an example where it is used on a schedule stored in a resource, which will pass it on to the System::check_change_tick of its systems.

use bevy_ecs::prelude::*;
use bevy_ecs::component::CheckChangeTicks;

#[derive(Resource)]
struct CustomSchedule(Schedule);

let mut world = World::new();
world.add_observer(|check: On<CheckChangeTicks>, mut schedule: ResMut<CustomSchedule>| {
    schedule.0.check_change_ticks(*check);
});

The observers are triggered by World::check_change_ticks which every schedule calls before running. This method also returns an Option<CheckChangeTicks> which is Some in case it was time to check the ticks.

Transform and GlobalTransform::compute_matrix rename #

GlobalTransform::compute_matrix has been renamed to GlobalTransform::to_matrix because it does not compute anything, it simply moves data into a different type. Transform::compute_matrix has been renamed to Transform::to_matrix for consistency with GlobalTransform.

Compressed image saver feature #

PRs:#19789

The compressed image saver has been gated behind its own dedicated feature flag now. If you were using it, you need to enable the "compressed_image_saver" feature.

Change filters container of LogDiagnosticsState to HashSet #

PRs:#19323

LogDiagnosticsState's filter container and the argument of LogDiagnosticPlugin::filtered is now a HashSet rather than a Vec.

FULLSCREEN_SHADER_HANDLE replaced with FullscreenShader #

PRs:#19426

FULLSCREEN_SHADER_HANDLE and fullscreen_shader_vertex_state have been replaced by the FullscreenShader resource. Users of either of these will need to call FullscreenShader::shader or FullscreenShader::to_vertex_state respectively. You may need to clone FullscreenShader out of the render world to store an instance that you can use later (e.g., if you are attempting to use the fullscreen shader inside a SpecializedRenderPipeline implementation).

For example, if your previous code looked like this:

struct MyPipeline {
  some_bind_group: BindGroupLayout,
}

impl FromWorld for MyPipeline {
  fn from_world(render_world: &mut World) -> Self {
    let some_bind_group = /* ... RenderDevice stuff */;
    Self {
      some_bind_group,
    }
  }
}

impl SpecializedRenderPipeline for MyPipeline {
  fn specialize(&self, key: Self::Key) -> RenderPipelineDescriptor {
    RenderPipelineDescriptor {
      vertex: fullscreen_shader_vertex_state(),
      // ... other stuff
    }
  }
}

You can migrate your code to:

struct MyPipeline {
  some_bind_group: BindGroupLayout,
  fullscreen_shader: FullscreenShader,
}

impl FromWorld for MyPipeline {
  fn from_world(render_world: &mut World) -> Self {
    let some_bind_group = /* ... RenderDevice stuff */;
    Self {
      some_bind_group,
      fullscreen_shader: render_world.resource::<FullscreenShader>().clone(),
    }
  }
}

impl SpecializedRenderPipeline for MyPipeline {
  fn specialize(&self, key: Self::Key) -> RenderPipelineDescriptor {
    RenderPipelineDescriptor {
      vertex: self.fullscreen_shader.to_vertex_state(),
      // ... other stuff
    }
  }
}

This is just one example. Pipelines may be initialized in different ways, but the primary strategy is clone out the FullscreenShader resource from the render world, and call to_vertex_state to use it as the vertex shader.

Smooth normals implementation changed #

PRs:#18552

In Bevy 0.16, Mesh smooth normal calculation used a triangle area-weighted algorithm. In 0.17, the area-weighted algorithm was moved to separate methods, the default implementation was switched to a corner angle-weighted algorithm, and Mesh::compute_custom_smooth_normals was added for other cases.

The angle-weighted method is more suitable for growing or shrinking a mesh along its vertex normals, such as when generating an outline mesh. It also results in more expected lighting behavior for some meshes. In most cases, the difference will be small and no change is needed. However, the new default is somewhat slower, and does not always produce the result desired by an artist. If you preferred the lighting in 0.16, or have a significant performance regression, or needed area-weighted normals for any other reason, you can switch to the new dedicated area-weighted methods.

// Only if the new smooth normals algorithm is unsatisfactory:

let mut mesh = Mesh::new(PrimitiveTopology::TriangleList, default())
    .with_inserted_attribute(Mesh::ATTRIBUTE_POSITION, positions)
-    .with_computed_smooth_normals();
+    .with_computed_area_weighted_normals;

- mesh.compute_smooth_normals();
+ mesh.compute_area_weighted_normals();

As part of this change, the helper functions face_normal and face_area_normal, were renamed to triangle_normal and triangle_area_normal respectively to better reflect the fact that they do not take an entire geometric face into account.

- use bevy::render::mesh::face_normal;
+ use bevy::render::mesh::triangle_normal;
- let normal = face_normal(a, b, c);
+ let normal = triangle_normal(a, b, c);

- use bevy::render::mesh::face_area_normal;
+ use bevy::render::mesh::triangle_area_normal;
- let normal = face_area_normal(a, b, c);
+ let normal = triangle_area_normal(a, b, c);

SpawnableList #

In order to reduce the stack size taken up by spawning and inserting large bundles, SpawnableList now takes a MovingPtr<T> as the self-type for the spawn function:

// 0.16
fn spawn(self, world: &mut World, entity: Entity);

let list = Spawn(my_bundle);
list.spawn(world, entity);

// 0.17
fn spawn(self: MovingPtr<'_, Self>, world: &mut World, entity: Entity);

let list = Spawn(my_bundle);
move_as_ptr!(list);
SpawnableList::spawn(list, world, entity);

This change also means that SpawnableList must now also be Sized!

MovingPtr<T> is a safe, typed, box-like pointer that owns the data it points to, but not the underlying memory: that means the owner of a MovingPtr<T> can freely move parts of the data out and doesn't have to worry about de-allocating memory. Much like Box<T>, MovingPtr<T> implements Deref and DerefMut for easy access to the stored type, when it's safe to do so. To decompose the value inside of the MovingPtr<T> into its fields without copying them to the stack, you can use the deconstruct_moving_ptr! macro to give you MovingPtr<U>s to each field specified:

struct MySpawnableList<A: Bundle, B: Bundle> {
    a: A,
    b: B,
}
let my_ptr: MovingPtr<'_, MySpawnableList<u32, String>> = ...;
deconstruct_moving_ptr!(my_ptr => { a, b, });
let a_ptr: MovingPtr<'_, u32> = a;
let b_ptr: MovingPtr<'_, String> = b;

Similar to Box::into_inner, MovingPtr<T> also has a method MovingPtr::read for moving the whole value out of the pointer onto the stack:

let a: u32 = a_ptr.read();
let b: String = b_ptr.read();

To create a MovingPtr<T> from a value, you can use the move_as_ptr! macro:

let my_value = MySpawnableList { a: 42u32, b: "Hello".to_string() };
move_as_ptr!(my_value);
let _: MovingPtr = my_value;

This macro works by shadowing the original variable name with the newly created MovingPtr<T>. MovingPtr<T> can also be created manually with the unsafe method MovingPtr::from_value, which takes a &mut to an initialized MaybeUninit<T>, which the MovingPtr<T> takes ownership of: the MaybeUninit<T> should be treated as uninitialized after the MovingPtr<T> has been used!

To migrate your implementations of SpawnableList to the new API, you will want to read the this parameter to spawn or insert any bundles stored within:


impl<R: Relationship> SpawnableList<R> for MySpawnableList<A: Bundle, B: Bundle> {
    // 0.16
    fn spawn(self, world: &mut World, entity: Entity) {
        let MySpawnableList { a, b } = self;
        world.spawn((R::from(entity), a, b));
    }

    // 0.17
    fn spawn(this: MovingPtr<'_, Self>, world: &mut World, entity: Entity) {
        let MySpawnableList { a, b } = this.read();
        world.spawn((R::from(entity), a, b));
    }
}

or only read the fields you need with deconstruct_moving_ptr!:

    fn spawn(this: MovingPtr<'_, Self>, world: &mut World, entity: Entity) {
        unsafe {
            // Only `a` is kept, `b` will be forgotten without being dropped!
            deconstruct_moving_ptr!(this => { a, });
            let a = a.read();
            world.spawn((R::from(entity), a));
        }
    }

Changes to bevy_tasks feature flags #

Various feature flags in bevy_tasks have been simplified, thanks to the addition of bevy_platform::cfg:

  • Removed critical-section feature (it was just a re-export of bevy_platform anyway)
  • Removed std and web features, relying on bevy_platform::cfg to check for availability.
  • Added futures-lite feature to provide access to the block_on implementation from futures-lite.
  • Added a fallback implementation of block_on that just busy-waits.
  • Moved wasm-bindgen related dependencies out of bevy_tasks and moved them into bevy_platform under a new exports module.
  • Made async-io implicit feature explicit.

Any std, web or critical-section feature flags that you've enabled for bevy_tasks in your project can simply be removed.

If you need access to wasm-bindgen functionality that was previously in bevy_tasks, you can find them in bevy_platform. However, note that the re-exports of various web-related crates (js_sys, wasm_bindgen and wasm_bindgen_futures) are not intended for external consumption. Instead, pull your own dependencies to these crates, making sure the version used matches to ensure interoperability.