Reframe old "scene" terminology as "world serialization" (#23630)

Part 2 of #23619 

In **Bevy 0.19** we are landing a subset of Bevy's Next Generation Scene
system (often known as BSN), which now lives in the `bevy_scene` /
`bevy::scene` crate. However the old `bevy_scene` system still needs to
stick around for a bit longer, as it provides some features that Bevy's
Next Generation Scene system doesn't (yet!):

1. It is not _yet_ possible to write a World _to_ BSN, so the old system
is still necessary for "round trip World serialization".
2. The GLTF scene loader has not yet been ported to BSN, so the old
system is still necessary to spawn GLTF scenes in Bevy.

For this reason, we have renamed the old `bevy_scene` crate to
`bevy_world_serialization`. If you were referencing `bevy_scene::*` or
`bevy::scene::*` types, rename those paths to
`bevy_world_serialization::*` and `bevy::world_serialization::*`
respectively.

Additionally, to avoid confusion / conflicts with the new scene system,
all "scene" terminology / types have been reframed as "world
serialization":

- `Scene` -> `WorldAsset` (as this was always just a World wrapper)
- `SceneRoot` -> `WorldAssetRoot`
- `DynamicScene` -> `DynamicWorld`
    - `DynamicScene::from_scene` -> `DynamicWorld::from_world_asset`
- `DynamicSceneBuilder` -> `DynamicWorldBuilder`
- `DynamicSceneRoot` -> `DynamicWorldRoot`
- `SceneInstanceReady` -> `WorldInstanceReady`
- `SceneLoader` -> `WorldAssetLoader`
- `ScenePlugin` -> `WorldSerializationPlugin`
- `SceneRootTemplate` -> `WorldAssetRootTemplate`
- `SceneSpawner` -> `WorldInstanceSpawner`
- `SceneFilter` -> `WorldFilter`
- `SceneLoaderError` -> `WorldAssetLoaderError`
- `SceneSpawnError` -> `WorldInstanceSpawnError`

Note that I went with `bevy_world_serialization` over
`bevy_ecs_serialization`, as that is what all of the internal features
described themselves as. I think it is both more specific and does a
better job of making itself decoupled from `bevy_ecs` proper.
This commit is contained in:
Carter Anderson
2026-04-03 17:31:47 -07:00
committed by GitHub
parent 53ddd5e615
commit 535cf401cc
95 changed files with 2055 additions and 1935 deletions
+1 -1
View File
@@ -32,7 +32,7 @@ example_showcase_config.ron
example-showcase-reports/
# Generated by "examples/scene/scene.rs"
assets/scenes/load_scene_example-new.scn.ron
assets/serialized_worlds/load_scene_example-new.scn.ron
# Generated by "examples/window/screenshot.rs"
**/screenshot-*.png
+7 -7
View File
@@ -164,7 +164,7 @@ audio = ["bevy_audio", "vorbis"]
audio-all-formats = ["bevy_audio", "aac", "flac", "mp3", "mp4", "vorbis", "wav"]
# COLLECTION: Features used to compose Bevy scenes.
scene = ["bevy_ecs_serialization", "bevy_scene2"]
scene = ["bevy_world_serialization", "bevy_scene2"]
# COLLECTION: Enables picking with all backends.
picking = ["bevy_picking", "mesh_picking", "sprite_picking", "ui_picking"]
@@ -318,7 +318,7 @@ bevy_picking = ["bevy_internal/bevy_picking"]
bevy_render = ["bevy_internal/bevy_render"]
# Provides ECS serialization functionality
bevy_ecs_serialization = ["bevy_internal/bevy_ecs_serialization"]
bevy_world_serialization = ["bevy_internal/bevy_world_serialization"]
# Provides scene functionality
bevy_scene2 = ["bevy_internal/bevy_scene2"]
@@ -3022,13 +3022,13 @@ wasm = false
# Scene
[[example]]
name = "scene"
path = "examples/scene/scene.rs"
name = "world_serialization"
path = "examples/scene/world_serialization.rs"
doc-scrape-examples = true
[package.metadata.example.scene]
name = "Scene"
description = "Demonstrates loading from and saving scenes to files"
[package.metadata.example.world_serialization]
name = "World Serialization"
description = "Demonstrates loading from and saving world to files"
category = "Scene"
wasm = false
@@ -0,0 +1,49 @@
---
title: "The old `bevy_scene` is now `bevy_world_serialization"
pull_requests: [23619, 23630]
---
In **Bevy 0.19** we landed a subset of Bevy's Next Generation Scene system (often known as BSN), which now lives in the `bevy_scene` / `bevy::scene` crate. However the old `bevy_scene` system still needs to stick around for a bit longer, as it provides some features that Bevy's Next Generation Scene system doesn't (yet!):
1. It is not _yet_ possible to write a World _to_ BSN, so the old system is still necessary for "round trip World serialization".
2. The GLTF scene loader has not yet been ported to BSN, so the old system is still necessary to spawn GLTF scenes in Bevy.
For this reason, we have renamed the old `bevy_scene` crate to `bevy_world_serialization`. If you were referencing `bevy_scene::*` or `bevy::scene::*` types, rename those paths to `bevy_world_serialization::*` and `bevy::world_serialization::*` respectively.
Additionally, to avoid confusion / conflicts with the new scene system, all "scene" terminology / types have been reframed as "world serialization":
- `Scene` -> `WorldAsset` (as this was always just a World wrapper)
- `SceneRoot` -> `WorldAssetRoot`
- `DynamicScene` -> `DynamicWorld`
- `DynamicScene::from_scene` -> `DynamicWorld::from_world_asset`
- `DynamicSceneBuilder` -> `DynamicWorldBuilder`
- `DynamicSceneRoot` -> `DynamicWorldRoot`
- `SceneInstanceReady` -> `WorldInstanceReady`
- `SceneLoader` -> `WorldAssetLoader`
- `ScenePlugin` -> `WorldSerializationPlugin`
- `SceneRootTemplate` -> `WorldAssetRootTemplate`
- `SceneSpawner` -> `WorldInstanceSpawner`
- `SceneFilter` -> `WorldFilter`
- `SceneLoaderError` -> `WorldAssetLoaderError`
- `SceneSpawnError` -> `WorldInstanceSpawnError`
GLTF scene spawning is the most likely source of breakage for most people, as round trip world serialization is a relatively niche use case. For most people, the migration should be as simple as:
```rust
// before
commands.spawn(SceneRoot(asset_server.load("scene.gltf#Scene0")));
// after
commands.spawn(WorldAssetRoot(asset_server.load("scene.gltf#Scene0")));
```
We know this naming is a bit awkward. Once we port GLTF loading over to BSN (hopefully in the next release), you will be able to do cool stuff like this:
```rust
bsn! {
:"scene.gltf#Scene0"
Transform { position: Vec3 { x: 10. } }
}
```
This would set _just_ the `x` position in the GLTF scene root to `x`, patching on top of the position defined in the gltf scene. Cool!
@@ -1,6 +1,6 @@
(
resources: {
"scene::ResourceA": (
"world_serialization::ResourceA": (
score: 1,
),
},
@@ -14,19 +14,19 @@
rotation: (0.0, 0.0, 0.0, 1.0),
scale: (1.0, 1.0, 1.0),
),
"scene::ComponentA": (
"world_serialization::ComponentA": (
x: 1.0,
y: 2.0,
),
"scene::ComponentB": (
"world_serialization::ComponentB": (
value: "hello",
),
"bevy_ecs_serialization::components::SceneRoot": (Path("models/FlightHelmet/FlightHelmet.gltf#Scene0")),
"bevy_world_serialization::components::WorldAssetRoot": (Path("models/FlightHelmet/FlightHelmet.gltf#Scene0")),
},
),
4294967298: (
components: {
"scene::ComponentA": (
"world_serialization::ComponentA": (
x: 3.0,
y: 4.0,
),
+2 -2
View File
@@ -203,9 +203,9 @@ pub struct AnimationSystems;
#[derive(Debug, Hash, PartialEq, Eq, Clone, SystemSet)]
pub enum SceneSpawnerSystems {
/// Bevy's original scene system.
SceneSpawn,
WorldInstanceSpawn,
/// Bevy's next-generation scene system
Scene2Spawn,
SceneSpawn,
}
/// Defines the schedules to be run for the [`Main`] schedule, including
-7
View File
@@ -1,7 +0,0 @@
# Bevy Scene
[![License](https://img.shields.io/badge/license-MIT%2FApache-blue.svg)](https://github.com/bevyengine/bevy#license)
[![Crates.io](https://img.shields.io/crates/v/bevy_ecs_serialization.svg)](https://crates.io/crates/bevy_ecs_serialization)
[![Downloads](https://img.shields.io/crates/d/bevy_ecs_serialization.svg)](https://crates.io/crates/bevy_ecs_serialization)
[![Docs](https://docs.rs/bevy_ecs_serialization/badge.svg)](https://docs.rs/bevy_ecs_serialization/latest/bevy_ecs_serialization/)
[![Discord](https://img.shields.io/discord/691052431525675048.svg?label=&logo=discord&logoColor=ffffff&color=7389D8&labelColor=6A7EC2)](https://discord.gg/bevy)
File diff suppressed because it is too large Load Diff
+1 -1
View File
@@ -33,7 +33,7 @@ bevy_mesh = { path = "../bevy_mesh", version = "0.19.0-dev", features = [
] }
bevy_reflect = { path = "../bevy_reflect", version = "0.19.0-dev" }
bevy_material = { path = "../bevy_material", version = "0.19.0-dev" }
bevy_ecs_serialization = { path = "../bevy_ecs_serialization", version = "0.19.0-dev" }
bevy_world_serialization = { path = "../bevy_world_serialization", version = "0.19.0-dev" }
bevy_transform = { path = "../bevy_transform", version = "0.19.0-dev" }
bevy_tasks = { path = "../bevy_tasks", version = "0.19.0-dev" }
bevy_platform = { path = "../bevy_platform", version = "0.19.0-dev", default-features = false, features = [
+4 -4
View File
@@ -6,10 +6,10 @@ use core::ops::Deref;
use bevy_animation::AnimationClip;
use bevy_asset::{Asset, Handle};
use bevy_ecs::{component::Component, reflect::ReflectComponent};
use bevy_ecs_serialization::Scene;
use bevy_mesh::{skinning::SkinnedMeshInverseBindposes, Mesh};
use bevy_platform::collections::HashMap;
use bevy_reflect::{prelude::ReflectDefault, Reflect, TypePath};
use bevy_world_serialization::WorldAsset;
use crate::{GltfAssetLabel, GltfMaterial};
@@ -17,9 +17,9 @@ use crate::{GltfAssetLabel, GltfMaterial};
#[derive(Asset, Debug, TypePath)]
pub struct Gltf {
/// All scenes loaded from the glTF file.
pub scenes: Vec<Handle<Scene>>,
pub scenes: Vec<Handle<WorldAsset>>,
/// Named scenes loaded from the glTF file.
pub named_scenes: HashMap<Box<str>, Handle<Scene>>,
pub named_scenes: HashMap<Box<str>, Handle<WorldAsset>>,
/// All meshes loaded from the glTF file.
pub meshes: Vec<Handle<GltfMesh>>,
/// Named meshes loaded from the glTF file.
@@ -37,7 +37,7 @@ pub struct Gltf {
/// Named skins loaded from the glTF file.
pub named_skins: HashMap<Box<str>, Handle<GltfSkin>>,
/// Default scene to be displayed.
pub default_scene: Option<Handle<Scene>>,
pub default_scene: Option<Handle<WorldAsset>>,
/// All animations loaded from the glTF file.
#[cfg(feature = "bevy_animation")]
pub animations: Vec<Handle<AnimationClip>>,
+2 -2
View File
@@ -54,7 +54,7 @@ impl ConvertCoordinates for [f32; 4] {
/// options will behave like so:
///
/// - `rotate_scene_entity` will make the glTF's scene forward align with the [`Transform::forward`]
/// of the entity with the [`SceneInstance`](bevy_ecs_serialization::SceneInstance) component.
/// of the entity with the [`WorldInstance`](bevy_world_serialization::WorldInstance) component.
/// - `rotate_meshes` will do the same for entities with a `Mesh3d` component.
///
/// Other entities in the scene are not converted, so their forward may not
@@ -63,7 +63,7 @@ impl ConvertCoordinates for [f32; 4] {
#[derive(Copy, Clone, Default, Debug, Serialize, Deserialize)]
pub struct GltfConvertCoordinates {
/// If true, convert scenes by rotating the top-level transform of the scene entity.
/// This will ensure that [`Transform::forward`] of the "root" entity (the one with [`SceneInstance`](bevy_ecs_serialization::SceneInstance))
/// This will ensure that [`Transform::forward`] of the "root" entity (the one with [`WorldInstance`](bevy_world_serialization::WorldInstance))
/// aligns with the "forward" of the glTF scene.
///
/// The scene entity is created by the glTF loader. Its parent is the entity
+7 -7
View File
@@ -9,11 +9,11 @@ use bevy_asset::AssetPath;
/// ```
/// # use bevy_ecs::prelude::*;
/// # use bevy_asset::prelude::*;
/// # use bevy_ecs_serialization::prelude::*;
/// # use bevy_world_serialization::prelude::*;
/// # use bevy_gltf::prelude::*;
///
/// fn load_gltf_scene(asset_server: Res<AssetServer>) {
/// let gltf_scene: Handle<Scene> = asset_server.load(GltfAssetLabel::Scene(0).from_asset("models/FlightHelmet/FlightHelmet.gltf"));
/// let gltf_scene: Handle<WorldAsset> = asset_server.load(GltfAssetLabel::Scene(0).from_asset("models/FlightHelmet/FlightHelmet.gltf"));
/// }
/// ```
///
@@ -22,16 +22,16 @@ use bevy_asset::AssetPath;
/// ```
/// # use bevy_ecs::prelude::*;
/// # use bevy_asset::prelude::*;
/// # use bevy_ecs_serialization::prelude::*;
/// # use bevy_world_serialization::prelude::*;
/// # use bevy_gltf::prelude::*;
///
/// fn load_gltf_scene(asset_server: Res<AssetServer>) {
/// let gltf_scene: Handle<Scene> = asset_server.load(format!("models/FlightHelmet/FlightHelmet.gltf#{}", GltfAssetLabel::Scene(0)));
/// let gltf_scene: Handle<WorldAsset> = asset_server.load(format!("models/FlightHelmet/FlightHelmet.gltf#{}", GltfAssetLabel::Scene(0)));
/// }
/// ```
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum GltfAssetLabel {
/// `Scene{}`: glTF Scene as a Bevy [`Scene`](bevy_ecs_serialization::Scene)
/// `Scene{}`: glTF Scene as a Bevy [`WorldAsset`](bevy_world_serialization::WorldAsset)
Scene(usize),
/// `Node{}`: glTF Node as a [`GltfNode`](crate::GltfNode)
Node(usize),
@@ -102,11 +102,11 @@ impl GltfAssetLabel {
/// ```
/// # use bevy_ecs::prelude::*;
/// # use bevy_asset::prelude::*;
/// # use bevy_ecs_serialization::prelude::*;
/// # use bevy_world_serialization::prelude::*;
/// # use bevy_gltf::prelude::*;
///
/// fn load_gltf_scene(asset_server: Res<AssetServer>) {
/// let gltf_scene: Handle<Scene> = asset_server.load(GltfAssetLabel::Scene(0).from_asset("models/FlightHelmet/FlightHelmet.gltf"));
/// let gltf_scene: Handle<WorldAsset> = asset_server.load(GltfAssetLabel::Scene(0).from_asset("models/FlightHelmet/FlightHelmet.gltf"));
/// }
/// ```
pub fn from_asset(&self, path: impl Into<AssetPath<'static>>) -> AssetPath<'static> {
+5 -5
View File
@@ -17,7 +17,7 @@
//! ```
//! # use bevy_ecs::prelude::*;
//! # use bevy_asset::prelude::*;
//! # use bevy_ecs_serialization::prelude::*;
//! # use bevy_world_serialization::prelude::*;
//! # use bevy_transform::prelude::*;
//! # use bevy_gltf::prelude::*;
//!
@@ -26,7 +26,7 @@
//! // This is equivalent to "models/FlightHelmet/FlightHelmet.gltf#Scene0"
//! // The `#Scene0` label here is very important because it tells bevy to load the first scene in the glTF file.
//! // If this isn't specified bevy doesn't know which part of the glTF file to load.
//! SceneRoot(asset_server.load(GltfAssetLabel::Scene(0).from_asset("models/FlightHelmet/FlightHelmet.gltf"))),
//! WorldAssetRoot(asset_server.load(GltfAssetLabel::Scene(0).from_asset("models/FlightHelmet/FlightHelmet.gltf"))),
//! // You can use the transform to give it a position
//! Transform::from_xyz(2.0, 0.0, -5.0),
//! ));
@@ -42,7 +42,7 @@
//! ```
//! # use bevy_ecs::prelude::*;
//! # use bevy_asset::prelude::*;
//! # use bevy_ecs_serialization::prelude::*;
//! # use bevy_world_serialization::prelude::*;
//! # use bevy_transform::prelude::*;
//! # use bevy_gltf::Gltf;
//!
@@ -72,11 +72,11 @@
//! *loaded = true;
//!
//! // Spawns the first scene in the file
//! commands.spawn(SceneRoot(gltf.scenes[0].clone()));
//! commands.spawn(WorldAssetRoot(gltf.scenes[0].clone()));
//!
//! // Spawns the scene named "Lenses_low"
//! commands.spawn((
//! SceneRoot(gltf.named_scenes["Lenses_low"].clone()),
//! WorldAssetRoot(gltf.named_scenes["Lenses_low"].clone()),
//! Transform::from_xyz(1.0, 2.0, 3.0),
//! ));
//! }
+5 -5
View File
@@ -21,7 +21,6 @@ use bevy_ecs::{
name::Name,
world::World,
};
use bevy_ecs_serialization::Scene;
use bevy_image::{
CompressedImageFormats, Image, ImageLoaderSettings, ImageSampler, ImageSamplerDescriptor,
ImageType, TextureError,
@@ -40,6 +39,7 @@ use bevy_reflect::TypePath;
#[cfg(not(target_arch = "wasm32"))]
use bevy_tasks::IoTaskPool;
use bevy_transform::components::Transform;
use bevy_world_serialization::WorldAsset;
use gltf::{
accessor::Iter,
image::Source,
@@ -1117,7 +1117,7 @@ impl GltfLoader {
);
}
let loaded_scene = scene_load_context.finish(Scene::new(world));
let loaded_scene = scene_load_context.finish(WorldAsset::new(world));
let scene_handle = load_context.add_loaded_labeled_asset(
GltfAssetLabel::Scene(scene.index()).to_string(),
loaded_scene,
@@ -2112,12 +2112,12 @@ mod test {
AssetApp, AssetLoader, AssetPlugin, AssetServer, Assets, Handle, LoadContext, LoadState,
};
use bevy_ecs::{resource::Resource, world::World};
use bevy_ecs_serialization::ScenePlugin;
use bevy_image::{Image, ImageLoaderSettings};
use bevy_log::LogPlugin;
use bevy_mesh::skinning::SkinnedMeshInverseBindposes;
use bevy_mesh::MeshPlugin;
use bevy_reflect::TypePath;
use bevy_world_serialization::WorldSerializationPlugin;
fn test_app(dir: Dir) -> App {
let mut app = App::new();
@@ -2130,7 +2130,7 @@ mod test {
LogPlugin::default(),
TaskPoolPlugin::default(),
AssetPlugin::default(),
ScenePlugin,
WorldSerializationPlugin,
MeshPlugin,
crate::GltfPlugin::default(),
));
@@ -2557,7 +2557,7 @@ mod test {
LogPlugin::default(),
TaskPoolPlugin::default(),
AssetPlugin::default(),
ScenePlugin,
WorldSerializationPlugin,
MeshPlugin,
crate::GltfPlugin::default(),
));
+4 -4
View File
@@ -120,7 +120,7 @@ serialize = [
"bevy_image?/serialize",
"bevy_input/serialize",
"bevy_math/serialize",
"bevy_ecs_serialization?/serialize",
"bevy_world_serialization?/serialize",
"bevy_time/serialize",
"bevy_transform/serialize",
"bevy_ui?/serialize",
@@ -235,7 +235,7 @@ bevy_mikktspace = ["bevy_mesh?/bevy_mikktspace"]
bevy_window = ["dep:bevy_window", "dep:bevy_a11y", "bevy_image"]
bevy_winit = ["dep:bevy_winit", "bevy_window"]
bevy_camera = ["dep:bevy_camera", "bevy_mesh", "bevy_window"]
bevy_ecs_serialization = ["dep:bevy_ecs_serialization", "bevy_asset"]
bevy_world_serialization = ["dep:bevy_world_serialization", "bevy_asset"]
bevy_material = ["dep:bevy_material", "bevy_image", "bevy_shader"]
bevy_light = ["dep:bevy_light", "bevy_camera"]
bevy_render = [
@@ -266,7 +266,7 @@ bevy_ui_render = ["dep:bevy_ui_render", "bevy_sprite_render", "bevy_ui"]
bevy_solari = ["dep:bevy_solari", "bevy_pbr"]
bevy_gizmos = ["dep:bevy_gizmos", "bevy_camera", "bevy_light?/bevy_gizmos"]
bevy_gizmos_render = ["dep:bevy_gizmos_render", "bevy_gizmos"]
bevy_gltf = ["dep:bevy_gltf", "bevy_ecs_serialization", "bevy_pbr?/bevy_gltf"]
bevy_gltf = ["dep:bevy_gltf", "bevy_world_serialization", "bevy_pbr?/bevy_gltf"]
# Used to disable code that is unsupported when Bevy is dynamically linked
dynamic_linking = ["bevy_diagnostic/dynamic_linking"]
@@ -534,7 +534,7 @@ bevy_picking = { path = "../bevy_picking", optional = true, version = "0.19.0-de
bevy_settings = { path = "../bevy_settings", optional = true, version = "0.19.0-dev" }
bevy_remote = { path = "../bevy_remote", optional = true, version = "0.19.0-dev" }
bevy_render = { path = "../bevy_render", optional = true, version = "0.19.0-dev" }
bevy_ecs_serialization = { path = "../bevy_ecs_serialization", optional = true, version = "0.19.0-dev" }
bevy_world_serialization = { path = "../bevy_world_serialization", optional = true, version = "0.19.0-dev" }
bevy_scene2 = { path = "../bevy_scene2", optional = true, version = "0.19.0-dev" }
bevy_solari = { path = "../bevy_solari", optional = true, version = "0.19.0-dev" }
bevy_sprite = { path = "../bevy_sprite", optional = true, version = "0.19.0-dev" }
+2 -2
View File
@@ -29,8 +29,8 @@ plugin_group! {
bevy_asset::io::web:::WebAssetPlugin,
#[cfg(feature = "bevy_asset")]
bevy_asset:::AssetPlugin,
#[cfg(feature = "bevy_ecs_serialization")]
bevy_ecs_serialization:::ScenePlugin,
#[cfg(feature = "bevy_world_serialization")]
bevy_world_serialization:::WorldSerializationPlugin,
#[cfg(feature = "bevy_scene2")]
bevy_scene2:::ScenePlugin,
// NOTE: WinitPlugin needs to be after AssetPlugin because of custom cursors.
+2 -2
View File
@@ -39,8 +39,6 @@ pub use bevy_core_pipeline as core_pipeline;
pub use bevy_dev_tools as dev_tools;
pub use bevy_diagnostic as diagnostic;
pub use bevy_ecs as ecs;
#[cfg(feature = "bevy_ecs_serialization")]
pub use bevy_ecs_serialization as scene;
#[cfg(feature = "bevy_feathers")]
pub use bevy_feathers as feathers;
#[cfg(feature = "bevy_gilrs")]
@@ -108,3 +106,5 @@ pub use bevy_utils as utils;
pub use bevy_window as window;
#[cfg(feature = "bevy_winit")]
pub use bevy_winit as winit;
#[cfg(feature = "bevy_world_serialization")]
pub use bevy_world_serialization as world_serialization;
+2 -2
View File
@@ -64,8 +64,8 @@ pub use crate::pbr::prelude::*;
pub use crate::render::prelude::*;
#[doc(hidden)]
#[cfg(feature = "bevy_ecs_serialization")]
pub use crate::scene::prelude::*;
#[cfg(feature = "bevy_world_serialization")]
pub use crate::world_serialization::prelude::*;
#[doc(hidden)]
#[cfg(feature = "bevy_sprite")]
+2 -2
View File
@@ -546,8 +546,8 @@ impl Plugin for ScenePlugin {
SpawnScene,
(resolve_scene_patches, spawn_queued)
.chain()
.in_set(SceneSpawnerSystems::Scene2Spawn)
.after(SceneSpawnerSystems::SceneSpawn),
.in_set(SceneSpawnerSystems::SceneSpawn)
.after(SceneSpawnerSystems::WorldInstanceSpawn),
)
.add_observer(on_add_scene_patch_instance);
}
@@ -1,8 +1,8 @@
[package]
name = "bevy_ecs_serialization"
name = "bevy_world_serialization"
version = "0.19.0-dev"
edition = "2024"
description = "Provides ECS serialization functionality for Bevy Engine"
description = "Provides ECS World serialization functionality for Bevy Engine"
homepage = "https://bevy.org"
repository = "https://github.com/bevyengine/bevy"
license = "MIT OR Apache-2.0"
@@ -0,0 +1,7 @@
# Bevy ECS Serialization
[![License](https://img.shields.io/badge/license-MIT%2FApache-blue.svg)](https://github.com/bevyengine/bevy#license)
[![Crates.io](https://img.shields.io/crates/v/bevy_world_serialization.svg)](https://crates.io/crates/bevy_world_serialization)
[![Downloads](https://img.shields.io/crates/d/bevy_world_serialization.svg)](https://crates.io/crates/bevy_world_serialization)
[![Docs](https://docs.rs/bevy_world_serialization/badge.svg)](https://docs.rs/bevy_world_serialization/latest/bevy_world_serialization/)
[![Discord](https://img.shields.io/discord/691052431525675048.svg?label=&logo=discord&logoColor=ffffff&color=7389D8&labelColor=6A7EC2)](https://discord.gg/bevy)
@@ -7,36 +7,39 @@ use derive_more::derive::From;
use bevy_camera::visibility::Visibility;
use crate::{DynamicScene, Scene};
use crate::{DynamicWorld, WorldAsset};
/// Adding this component will spawn the scene as a child of that entity.
/// Once it's spawned, the entity will have a [`SceneInstance`](crate::SceneInstance) component.
/// Adding this component will spawn the world as a child of that entity.
/// Once it's spawned, the entity will have a [`WorldInstance`](crate::WorldInstance) component.
///
/// Note: This was recently renamed from `WorldAssetRoot`, in the interest of giving "scene" terminology to
/// Bevy's next generation scene system, available in `bevy_scene`.
#[derive(
Component, FromTemplate, Clone, Debug, Default, Deref, DerefMut, Reflect, PartialEq, Eq, From,
)]
#[reflect(Component, Default, Debug, PartialEq, Clone)]
#[require(Transform)]
#[require(Visibility)]
pub struct SceneRoot(pub Handle<Scene>);
pub struct WorldAssetRoot(pub Handle<WorldAsset>);
impl AsAssetId for SceneRoot {
type Asset = Scene;
impl AsAssetId for WorldAssetRoot {
type Asset = WorldAsset;
fn as_asset_id(&self) -> AssetId<Self::Asset> {
self.id()
}
}
/// Adding this component will spawn the scene as a child of that entity.
/// Once it's spawned, the entity will have a [`SceneInstance`](crate::SceneInstance) component.
/// Adding this component will spawn the world as a child of that entity.
/// Once it's spawned, the entity will have a [`WorldInstance`](crate::WorldInstance) component.
#[derive(Component, Clone, Debug, Default, Deref, DerefMut, Reflect, PartialEq, Eq, From)]
#[reflect(Component, Default, Debug, PartialEq, Clone)]
#[require(Transform)]
#[require(Visibility)]
pub struct DynamicSceneRoot(pub Handle<DynamicScene>);
pub struct DynamicWorldRoot(pub Handle<DynamicWorld>);
impl AsAssetId for DynamicSceneRoot {
type Asset = DynamicScene;
impl AsAssetId for DynamicWorldRoot {
type Asset = DynamicWorld;
fn as_asset_id(&self) -> AssetId<Self::Asset> {
self.id()
@@ -1,4 +1,4 @@
use crate::{DynamicSceneBuilder, Scene, SceneSpawnError};
use crate::{DynamicWorldBuilder, WorldAsset, WorldInstanceSpawnError};
use bevy_asset::Asset;
use bevy_ecs::reflect::ReflectResource;
use bevy_ecs::{
@@ -12,26 +12,26 @@ use bevy_ecs::component::ComponentCloneBehavior;
use bevy_ecs::relationship::RelationshipHookMode;
#[cfg(feature = "serialize")]
use {crate::serde::SceneSerializer, bevy_reflect::TypeRegistry, serde::Serialize};
use {crate::serde::DynamicWorldSerializer, bevy_reflect::TypeRegistry, serde::Serialize};
/// A collection of serializable resources and dynamic entities.
///
/// Each dynamic entity in the collection contains its own run-time defined set of components.
/// To spawn a dynamic scene, you can use either:
/// * [`SceneSpawner::spawn_dynamic`](crate::SceneSpawner::spawn_dynamic)
/// * adding the [`DynamicSceneRoot`](crate::components::DynamicSceneRoot) component to an entity.
/// * using the [`DynamicSceneBuilder`] to construct a `DynamicScene` from `World`.
/// To spawn a dynamic world, you can use either:
/// * [`WorldInstanceSpawner::spawn_dynamic`](crate::WorldInstanceSpawner::spawn_dynamic)
/// * adding the [`DynamicWorldRoot`](crate::components::DynamicWorldRoot) component to an entity.
/// * using the [`DynamicWorldBuilder`] to construct a `DynamicWorld` from `World`.
#[derive(Asset, TypePath, Default)]
pub struct DynamicScene {
/// Resources stored in the dynamic scene.
pub struct DynamicWorld {
/// Resources stored in the dynamic world.
pub resources: Vec<Box<dyn PartialReflect>>,
/// Entities contained in the dynamic scene.
/// Entities contained in the dynamic world.
pub entities: Vec<DynamicEntity>,
}
/// A reflection-powered serializable representation of an entity and its components.
pub struct DynamicEntity {
/// The identifier of the entity, unique within a scene (and the world it may have been generated from).
/// The identifier of the entity, unique within a [`DynamicWorld`] (and the world it may have been generated from).
///
/// Components that reference this entity must consistently use this identifier.
pub entity: Entity,
@@ -40,19 +40,19 @@ pub struct DynamicEntity {
pub components: Vec<Box<dyn PartialReflect>>,
}
impl DynamicScene {
/// Create a new dynamic scene from a given scene.
impl DynamicWorld {
/// Create a new dynamic world from a given world.
///
/// The `type_registry` provides type information for extracting components and resources
/// through reflection. You can get this registry from the **main** world, using
/// `main_world.resource::<AppTypeRegistry>().read()` or `app_type_registry_res.read()`
/// (for `Res<AppTypeRegistry>`). Note: the scene world is unlikely to have a type registry
/// (for `Res<AppTypeRegistry>`). Note: the `world` is unlikely to have a type registry
/// internally.
pub fn from_scene(scene: &Scene, type_registry: &TypeRegistry) -> Self {
Self::from_world_with(&scene.world, type_registry)
pub fn from_world_asset(world: &WorldAsset, type_registry: &TypeRegistry) -> Self {
Self::from_world_with(&world.world, type_registry)
}
/// Create a new dynamic scene from a given world.
/// Create a new dynamic world from a given world.
///
/// Panics if `world` does not contain [`AppTypeRegistry`]. Use [`Self::from_world_with`] to
/// handle this case.
@@ -61,16 +61,16 @@ impl DynamicScene {
Self::from_world_with(world, &type_registry)
}
/// Create a new dynamic scene from a given world.
/// Create a new dynamic world from a given world.
///
/// The `type_registry` provides type information for extracting components and resources
/// through reflection. If the `world` is the "real" world (e.g., not a world in a [`Scene`]),
/// through reflection. If the `world` is the "real" world (e.g., not a world in a [`WorldAsset`]),
/// the `world` will contain the registry, which can be acquired using
/// `world.resource::<AppTypeRegistry>().read()`. For extracting from "scene worlds", you
/// will need to get the type registry from the main world (you can clone the `AppTypeRegistry`
/// out of the world to avoid borrowing the world itself).
pub fn from_world_with(world: &World, type_registry: &TypeRegistry) -> Self {
DynamicSceneBuilder::from_world(world, type_registry)
DynamicWorldBuilder::from_world(world, type_registry)
.extract_entities(
// we do this instead of a query, in order to completely sidestep default query filters.
// while we could use `Allow<_>`, this wouldn't account for custom disabled components
@@ -86,7 +86,7 @@ impl DynamicScene {
/// Write the resources, the dynamic entities, and their corresponding components to the given world.
///
/// This method will return a [`SceneSpawnError`] if a type either is not registered
/// This method will return a [`WorldInstanceSpawnError`] if a type either is not registered
/// in the provided [`AppTypeRegistry`] resource, or doesn't reflect the
/// [`Component`](bevy_ecs::component::Component) or [`Resource`](bevy_ecs::prelude::Resource) trait.
pub fn write_to_world_with(
@@ -94,39 +94,39 @@ impl DynamicScene {
world: &mut World,
entity_map: &mut EntityHashMap<Entity>,
type_registry: &TypeRegistry,
) -> Result<(), SceneSpawnError> {
// First ensure that every entity in the scene has a corresponding world
) -> Result<(), WorldInstanceSpawnError> {
// First ensure that every entity in the dynamic world has a corresponding world
// entity in the entity map.
for scene_entity in &self.entities {
for dynamic_entity in &self.entities {
// Fetch the entity with the given entity id from the `entity_map`
// or spawn a new entity with a transiently unique id if there is
// no corresponding entry.
entity_map
.entry(scene_entity.entity)
.entry(dynamic_entity.entity)
.or_insert_with(|| world.spawn_empty().id());
}
for scene_entity in &self.entities {
for dynamic_entity in &self.entities {
// Fetch the entity with the given entity id from the `entity_map`.
let entity = *entity_map
.get(&scene_entity.entity)
.get(&dynamic_entity.entity)
.expect("should have previously spawned an empty entity");
// Apply/ add each component to the given entity.
for component in &scene_entity.components {
for component in &dynamic_entity.components {
let type_info = component.get_represented_type_info().ok_or_else(|| {
SceneSpawnError::NoRepresentedType {
WorldInstanceSpawnError::NoRepresentedType {
type_path: component.reflect_type_path().to_string(),
}
})?;
let registration = type_registry.get(type_info.type_id()).ok_or_else(|| {
SceneSpawnError::UnregisteredButReflectedType {
WorldInstanceSpawnError::UnregisteredButReflectedType {
type_path: type_info.type_path().to_string(),
}
})?;
let reflect_component =
registration.data::<ReflectComponent>().ok_or_else(|| {
SceneSpawnError::UnregisteredComponent {
WorldInstanceSpawnError::UnregisteredComponent {
type_path: type_info.type_path().to_string(),
}
})?;
@@ -161,17 +161,17 @@ impl DynamicScene {
// This ensures the entities are available for the resources to reference during mapping.
for resource in &self.resources {
let type_info = resource.get_represented_type_info().ok_or_else(|| {
SceneSpawnError::NoRepresentedType {
WorldInstanceSpawnError::NoRepresentedType {
type_path: resource.reflect_type_path().to_string(),
}
})?;
let registration = type_registry.get(type_info.type_id()).ok_or_else(|| {
SceneSpawnError::UnregisteredButReflectedType {
WorldInstanceSpawnError::UnregisteredButReflectedType {
type_path: type_info.type_path().to_string(),
}
})?;
registration.data::<ReflectResource>().ok_or_else(|| {
SceneSpawnError::UnregisteredResource {
WorldInstanceSpawnError::UnregisteredResource {
type_path: type_info.type_path().to_string(),
}
})?;
@@ -205,29 +205,29 @@ impl DynamicScene {
/// Write the resources, the dynamic entities, and their corresponding components to the given world.
///
/// This method will return a [`SceneSpawnError`] if a type either is not registered
/// This method will return a [`WorldInstanceSpawnError`] if a type either is not registered
/// in the world's [`AppTypeRegistry`] resource, or doesn't reflect the
/// [`Component`](bevy_ecs::component::Component) trait.
pub fn write_to_world(
&self,
world: &mut World,
entity_map: &mut EntityHashMap<Entity>,
) -> Result<(), SceneSpawnError> {
) -> Result<(), WorldInstanceSpawnError> {
let registry = world.resource::<AppTypeRegistry>().clone();
self.write_to_world_with(world, entity_map, &registry.read())
}
// TODO: move to AssetSaver when it is implemented
/// Serialize this dynamic scene into the official Bevy scene format (`.scn` / `.scn.ron`).
/// Serialize this dynamic world into the serialized Bevy world format (`.scn` / `.scn.ron`).
///
/// The Bevy scene format is based on [Rusty Object Notation (RON)]. It describes the scene
/// in a human-friendly format. To deserialize the scene, use the [`SceneLoader`].
/// The serialized Bevy world format is based on [Rusty Object Notation (RON)]. It describes the world
/// in a human-friendly format. To deserialize the format, use the [`WorldAssetLoader`].
///
/// [`SceneLoader`]: crate::SceneLoader
/// [`WorldAssetLoader`]: crate::WorldAssetLoader
/// [Rusty Object Notation (RON)]: https://crates.io/crates/ron
#[cfg(feature = "serialize")]
pub fn serialize(&self, registry: &TypeRegistry) -> Result<String, ron::Error> {
serialize_ron(SceneSerializer::new(self, registry))
serialize_ron(DynamicWorldSerializer::new(self, registry))
}
}
@@ -256,8 +256,8 @@ mod tests {
use bevy_reflect::Reflect;
use crate::dynamic_scene::DynamicScene;
use crate::dynamic_scene_builder::DynamicSceneBuilder;
use crate::dynamic_world::DynamicWorld;
use crate::dynamic_world_builder::DynamicWorldBuilder;
#[derive(Resource, Reflect, MapEntities, Debug)]
#[reflect(Resource, MapEntities)]
@@ -283,10 +283,10 @@ mod tests {
entity_b: original_entity_b,
});
// Write the scene.
let scene = {
// Write the dynamic world.
let dynamic_world = {
let type_registry = app_type_registry.read();
DynamicSceneBuilder::from_world(&source_world, &type_registry)
DynamicWorldBuilder::from_world(&source_world, &type_registry)
.extract_resources()
.extract_entity(original_entity_a)
.extract_entity(original_entity_b)
@@ -297,7 +297,7 @@ mod tests {
let mut destination_world = World::new();
destination_world.insert_resource(app_type_registry);
scene
dynamic_world
.write_to_world(&mut destination_world, &mut entity_map)
.unwrap();
@@ -310,8 +310,8 @@ mod tests {
}
#[test]
fn components_not_defined_in_scene_should_not_be_affected_by_scene_entity_map() {
// Testing that scene reloading applies EntityMap correctly to MapEntities components.
fn components_not_defined_in_dynamic_world_should_not_be_affected_by_scene_entity_map() {
// Testing that dynamic world reloading applies EntityMap correctly to MapEntities components.
// First, we create a simple world with a parent and a child relationship
let mut world = World::new();
@@ -326,32 +326,36 @@ mod tests {
.entity_mut(original_parent_entity)
.add_child(original_child_entity);
// We then write this relationship to a new scene, and then write that scene back to the
// We then write this relationship to a new dynamic world, and then write that dynamic world back to the
// world to create another parent and child relationship
let scene = {
let dynamic_world = {
let type_registry = world.resource::<AppTypeRegistry>().read();
DynamicSceneBuilder::from_world(&world, &type_registry)
DynamicWorldBuilder::from_world(&world, &type_registry)
.extract_entity(original_parent_entity)
.extract_entity(original_child_entity)
.build()
};
let mut entity_map = EntityHashMap::default();
scene.write_to_world(&mut world, &mut entity_map).unwrap();
dynamic_world
.write_to_world(&mut world, &mut entity_map)
.unwrap();
let &from_scene_parent_entity = entity_map.get(&original_parent_entity).unwrap();
let &from_scene_child_entity = entity_map.get(&original_child_entity).unwrap();
let &from_dynamic_parent_entity = entity_map.get(&original_parent_entity).unwrap();
let &from_dynamic_child_entity = entity_map.get(&original_child_entity).unwrap();
// We then add the parent from the scene as a child of the original child
// We then add the parent from the dynamic world as a child of the original child
// Hierarchy should look like:
// Original Parent <- Original Child <- Scene Parent <- Scene Child
// Original Parent <- Original Child <- Dynamic World Parent <- Dynamic World Child
world
.entity_mut(original_child_entity)
.add_child(from_scene_parent_entity);
.add_child(from_dynamic_parent_entity);
// We then reload the scene to make sure that from_scene_parent_entity's parent component
// isn't updated with the entity map, since this component isn't defined in the scene.
// We then reload the dynamic world to make sure that from_dynamic_world_parent_entity's parent component
// isn't updated with the entity map, since this component isn't defined in the dynamic world.
// With [`bevy_ecs::hierarchy`], this can cause serious errors and malformed hierarchies.
scene.write_to_world(&mut world, &mut entity_map).unwrap();
dynamic_world
.write_to_world(&mut world, &mut entity_map)
.unwrap();
assert_eq!(
original_parent_entity,
@@ -361,27 +365,27 @@ mod tests {
.get::<ChildOf>()
.unwrap()
.parent(),
"something about reloading the scene is touching entities with the same scene Ids"
"something about reloading the dynamic world is touching entities with the same dynamic world Ids"
);
assert_eq!(
original_child_entity,
world
.get_entity(from_scene_parent_entity)
.get_entity(from_dynamic_parent_entity)
.unwrap()
.get::<ChildOf>()
.unwrap()
.parent(),
"something about reloading the scene is touching components not defined in the scene but on entities defined in the scene"
"something about reloading the dynamic world is touching components not defined in the dynamic world but on entities defined in the dynamic world"
);
assert_eq!(
from_scene_parent_entity,
from_dynamic_parent_entity,
world
.get_entity(from_scene_child_entity)
.get_entity(from_dynamic_child_entity)
.unwrap()
.get::<ChildOf>()
.expect("something is wrong with this test, and the scene components don't have a parent/child relationship")
.expect("something is wrong with this test, and the dynamic world components don't have a parent/child relationship")
.parent(),
"something is wrong with this test or the code reloading scenes since the relationship between scene entities is broken"
"something is wrong with this test or the code reloading dynamic worlds since the relationship between dynamic world entities is broken"
);
}
@@ -410,10 +414,10 @@ mod tests {
reg_write.register::<B>();
}
let mut scene_world = World::new();
scene_world.insert_resource(reg.clone());
scene_world.spawn((B(Entity::PLACEHOLDER), A));
let scene = DynamicScene::from_world(&scene_world);
let mut world = World::new();
world.insert_resource(reg.clone());
world.spawn((B(Entity::PLACEHOLDER), A));
let dynamic_world = DynamicWorld::from_world(&world);
let mut dst_world = World::new();
dst_world
@@ -427,7 +431,7 @@ mod tests {
// Prior to fix, the `Entities::alloc` call in
// `EntityMapper::map_entity` would panic due to pending entities from the observer
// not having been flushed.
scene
dynamic_world
.write_to_world(&mut dst_world, &mut Default::default())
.unwrap();
}
@@ -1,7 +1,7 @@
use core::any::TypeId;
use crate::reflect_utils::clone_reflect_value;
use crate::{DynamicEntity, DynamicScene, SceneFilter};
use crate::{DynamicEntity, DynamicWorld, WorldFilter};
use alloc::collections::BTreeMap;
use bevy_ecs::resource::IS_RESOURCE;
use bevy_ecs::{
@@ -15,14 +15,14 @@ use bevy_ecs::{
use bevy_reflect::{PartialReflect, TypeRegistry};
use bevy_utils::default;
/// A [`DynamicScene`] builder, used to build a scene from a [`World`] by extracting some entities and resources.
/// A [`DynamicWorld`] builder, used to build a [`DynamicWorld`] from a [`World`] by extracting some entities and resources.
///
/// # Component Extraction
///
/// By default, all components registered with [`ReflectComponent`] type data in a world's [`AppTypeRegistry`] will be extracted.
/// (this type data is added automatically during registration if [`Reflect`] is derived with the `#[reflect(Component)]` attribute).
/// This can be changed by [specifying a filter](DynamicSceneBuilder::with_component_filter) or by explicitly
/// [allowing](DynamicSceneBuilder::allow_component)/[denying](DynamicSceneBuilder::deny_component) certain components.
/// This can be changed by [specifying a filter](DynamicWorldBuilder::with_component_filter) or by explicitly
/// [allowing](DynamicWorldBuilder::allow_component)/[denying](DynamicWorldBuilder::deny_component) certain components.
///
/// Extraction happens immediately and uses the filter as it exists during the time of extraction.
///
@@ -30,8 +30,8 @@ use bevy_utils::default;
///
/// By default, all resources registered with [`ReflectResource`] type data in a world's [`AppTypeRegistry`] will be extracted.
/// (this type data is added automatically during registration if [`Reflect`] is derived with the `#[reflect(Resource)]` attribute).
/// This can be changed by [specifying a filter](DynamicSceneBuilder::with_resource_filter) or by explicitly
/// [allowing](DynamicSceneBuilder::allow_resource)/[denying](DynamicSceneBuilder::deny_resource) certain resources.
/// This can be changed by [specifying a filter](DynamicWorldBuilder::with_resource_filter) or by explicitly
/// [allowing](DynamicWorldBuilder::allow_resource)/[denying](DynamicWorldBuilder::deny_resource) certain resources.
///
/// Extraction happens immediately and uses the filter as it exists during the time of extraction.
///
@@ -43,7 +43,7 @@ use bevy_utils::default;
///
/// # Example
/// ```
/// # use bevy_ecs_serialization::DynamicSceneBuilder;
/// # use bevy_world_serialization::DynamicWorldBuilder;
/// # use bevy_ecs::reflect::AppTypeRegistry;
/// # use bevy_ecs::{
/// # component::Component, prelude::Entity, query::With, reflect::ReflectComponent, world::World,
@@ -56,58 +56,58 @@ use bevy_utils::default;
/// # world.init_resource::<AppTypeRegistry>();
/// # let entity = world.spawn(ComponentA).id();
/// let type_registry = world.resource::<AppTypeRegistry>().read();
/// let dynamic_scene = DynamicSceneBuilder::from_world(&world, &type_registry)
/// let dynamic_world = DynamicWorldBuilder::from_world(&world, &type_registry)
/// .extract_entity(entity)
/// .build();
/// ```
///
/// [`AppTypeRegistry`]: bevy_ecs::reflect::AppTypeRegistry
/// [`Reflect`]: bevy_reflect::Reflect
pub struct DynamicSceneBuilder<'w> {
pub struct DynamicWorldBuilder<'w> {
/// The resources that have been extracted so far.
extracted_resources: BTreeMap<ComponentId, Box<dyn PartialReflect>>,
/// The entities that have been extracted so far.
extracted_scene: BTreeMap<Entity, DynamicEntity>,
extracted_entities: BTreeMap<Entity, DynamicEntity>,
/// The filter to determine which components extract.
component_filter: SceneFilter,
component_filter: WorldFilter,
/// The filter to determine which resources to extract.
resource_filter: SceneFilter,
/// The world from which to build the scene.
resource_filter: WorldFilter,
/// The world from which to build the dynamic world.
original_world: &'w World,
/// The type registry to use for extracting items from the world.
type_registry: &'w TypeRegistry,
}
impl<'w> DynamicSceneBuilder<'w> {
impl<'w> DynamicWorldBuilder<'w> {
/// Prepare a builder that will extract entities and their component from the given [`World`].
///
/// The `type_registry` provides type information for extracting components and resources
/// through reflection. If the `world` is the "real" world (e.g., not a world in a
/// [`Scene`](crate::Scene)), the `world` will contain the registry, which can be acquired using
/// `world.resource::<AppTypeRegistry>().read()`. For extracting from "scene worlds", you
/// [`WorldAsset`](crate::WorldAsset)), the `world` will contain the registry, which can be acquired using
/// `world.resource::<AppTypeRegistry>().read()`. For extracting from "serialized worlds", you
/// will need to get the type registry from the main world (you can clone the `AppTypeRegistry`
/// out of the world to avoid borrowing the world itself).
pub fn from_world(world: &'w World, type_registry: &'w TypeRegistry) -> Self {
Self {
extracted_resources: default(),
extracted_scene: default(),
component_filter: SceneFilter::default(),
resource_filter: SceneFilter::default(),
extracted_entities: default(),
component_filter: WorldFilter::default(),
resource_filter: WorldFilter::default(),
original_world: world,
type_registry,
}
}
/// Specify a custom component [`SceneFilter`] to be used with this builder.
/// Specify a custom component [`WorldFilter`] to be used with this builder.
#[must_use]
pub fn with_component_filter(mut self, filter: SceneFilter) -> Self {
pub fn with_component_filter(mut self, filter: WorldFilter) -> Self {
self.component_filter = filter;
self
}
/// Specify a custom resource [`SceneFilter`] to be used with this builder.
/// Specify a custom resource [`WorldFilter`] to be used with this builder.
#[must_use]
pub fn with_resource_filter(mut self, filter: SceneFilter) -> Self {
pub fn with_resource_filter(mut self, filter: WorldFilter) -> Self {
self.resource_filter = filter;
self
}
@@ -117,8 +117,8 @@ impl<'w> DynamicSceneBuilder<'w> {
/// This is useful for resetting the filter so that types may be selectively denied
/// with [`deny_component`](`Self::deny_component`) and [`deny_resource`](`Self::deny_resource`).
pub fn allow_all(mut self) -> Self {
self.component_filter = SceneFilter::allow_all();
self.resource_filter = SceneFilter::allow_all();
self.component_filter = WorldFilter::allow_all();
self.resource_filter = WorldFilter::allow_all();
self
}
@@ -127,12 +127,12 @@ impl<'w> DynamicSceneBuilder<'w> {
/// This is useful for resetting the filter so that types may be selectively allowed
/// with [`allow_component`](`Self::allow_component`) and [`allow_resource`](`Self::allow_resource`).
pub fn deny_all(mut self) -> Self {
self.component_filter = SceneFilter::deny_all();
self.resource_filter = SceneFilter::deny_all();
self.component_filter = WorldFilter::deny_all();
self.resource_filter = WorldFilter::deny_all();
self
}
/// Allows the given component type, `T`, to be included in the generated scene.
/// Allows the given component type, `T`, to be included in the dynamic world.
///
/// This method may be called multiple times for any number of components.
///
@@ -144,7 +144,7 @@ impl<'w> DynamicSceneBuilder<'w> {
self
}
/// Denies the given component type, `T`, from being included in the generated scene.
/// Denies the given component type, `T`, from being included in the dynamic world.
///
/// This method may be called multiple times for any number of components.
///
@@ -163,7 +163,7 @@ impl<'w> DynamicSceneBuilder<'w> {
/// [denied]: Self::deny_component
#[must_use]
pub fn allow_all_components(mut self) -> Self {
self.component_filter = SceneFilter::allow_all();
self.component_filter = WorldFilter::allow_all();
self
}
@@ -174,11 +174,11 @@ impl<'w> DynamicSceneBuilder<'w> {
/// [allowed]: Self::allow_component
#[must_use]
pub fn deny_all_components(mut self) -> Self {
self.component_filter = SceneFilter::deny_all();
self.component_filter = WorldFilter::deny_all();
self
}
/// Allows the given resource type, `T`, to be included in the generated scene.
/// Allows the given resource type, `T`, to be included in the dynamic world.
///
/// This method may be called multiple times for any number of resources.
///
@@ -190,7 +190,7 @@ impl<'w> DynamicSceneBuilder<'w> {
self
}
/// Denies the given resource type, `T`, from being included in the generated scene.
/// Denies the given resource type, `T`, from being included in the dynamic world.
///
/// This method may be called multiple times for any number of resources.
///
@@ -209,7 +209,7 @@ impl<'w> DynamicSceneBuilder<'w> {
/// [denied]: Self::deny_resource
#[must_use]
pub fn allow_all_resources(mut self) -> Self {
self.resource_filter = SceneFilter::allow_all();
self.resource_filter = WorldFilter::allow_all();
self
}
@@ -220,19 +220,19 @@ impl<'w> DynamicSceneBuilder<'w> {
/// [allowed]: Self::allow_resource
#[must_use]
pub fn deny_all_resources(mut self) -> Self {
self.resource_filter = SceneFilter::deny_all();
self.resource_filter = WorldFilter::deny_all();
self
}
/// Consume the builder, producing a [`DynamicScene`].
/// Consume the builder, producing a [`DynamicWorld`].
///
/// To make sure the dynamic scene doesn't contain entities without any components, call
/// [`Self::remove_empty_entities`] before building the scene.
/// To make sure the dynamic world doesn't contain entities without any components, call
/// [`Self::remove_empty_entities`] before building the dynamic world.
#[must_use]
pub fn build(self) -> DynamicScene {
DynamicScene {
pub fn build(self) -> DynamicWorld {
DynamicWorld {
resources: self.extracted_resources.into_values().collect(),
entities: self.extracted_scene.into_values().collect(),
entities: self.extracted_entities.into_values().collect(),
}
}
@@ -249,7 +249,7 @@ impl<'w> DynamicSceneBuilder<'w> {
/// These were likely created because none of their components were present in the provided type registry upon extraction.
#[must_use]
pub fn remove_empty_entities(mut self) -> Self {
self.extracted_scene
self.extracted_entities
.retain(|_, entity| !entity.components.is_empty());
self
@@ -264,7 +264,7 @@ impl<'w> DynamicSceneBuilder<'w> {
///
/// This method may be used to extract entities from a query:
/// ```
/// # use bevy_ecs_serialization::DynamicSceneBuilder;
/// # use bevy_world_serialization::DynamicWorldBuilder;
/// # use bevy_ecs::reflect::AppTypeRegistry;
/// # use bevy_ecs::{
/// # component::Component, prelude::Entity, query::With, reflect::ReflectComponent, world::World,
@@ -280,7 +280,7 @@ impl<'w> DynamicSceneBuilder<'w> {
/// let mut query = world.query_filtered::<Entity, With<MyComponent>>();
///
/// let type_registry = world.resource::<AppTypeRegistry>().read();
/// let scene = DynamicSceneBuilder::from_world(&world, &type_registry)
/// let dynamic_world = DynamicWorldBuilder::from_world(&world, &type_registry)
/// .extract_entities(query.iter(&world))
/// .build();
/// ```
@@ -292,7 +292,7 @@ impl<'w> DynamicSceneBuilder<'w> {
#[must_use]
pub fn extract_entities(mut self, entities: impl Iterator<Item = Entity>) -> Self {
for entity in entities {
if self.extracted_scene.contains_key(&entity) {
if self.extracted_entities.contains_key(&entity) {
continue;
}
@@ -335,7 +335,7 @@ impl<'w> DynamicSceneBuilder<'w> {
};
extract_and_push();
}
self.extracted_scene.insert(entity, entry);
self.extracted_entities.insert(entity, entry);
}
self
@@ -349,7 +349,7 @@ impl<'w> DynamicSceneBuilder<'w> {
/// [`deny_resource`] helper methods.
///
/// ```
/// # use bevy_ecs_serialization::DynamicSceneBuilder;
/// # use bevy_world_serialization::DynamicWorldBuilder;
/// # use bevy_ecs::reflect::AppTypeRegistry;
/// # use bevy_ecs::prelude::{ReflectResource, Resource, World};
/// # use bevy_reflect::Reflect;
@@ -362,9 +362,9 @@ impl<'w> DynamicSceneBuilder<'w> {
/// world.insert_resource(MyResource);
///
/// let type_registry = world.resource::<AppTypeRegistry>().read();
/// let mut builder = DynamicSceneBuilder::from_world(&world, &type_registry)
/// let mut builder = DynamicWorldBuilder::from_world(&world, &type_registry)
/// .extract_resources();
/// let scene = builder.build();
/// let dynamic_world = builder.build();
/// ```
///
/// [`allow_resource`]: Self::allow_resource
@@ -427,7 +427,7 @@ mod tests {
use bevy_reflect::{Reflect, TypeRegistry};
use super::DynamicSceneBuilder;
use super::DynamicWorldBuilder;
#[derive(Component, Reflect, Default, Eq, PartialEq, Debug)]
#[reflect(Component)]
@@ -454,14 +454,14 @@ mod tests {
let entity = world.spawn((ComponentA, ComponentB)).id();
let scene = DynamicSceneBuilder::from_world(&world, &type_registry)
let dynamic_world = DynamicWorldBuilder::from_world(&world, &type_registry)
.extract_entity(entity)
.build();
assert_eq!(scene.entities.len(), 1);
assert_eq!(scene.entities[0].entity, entity);
assert_eq!(scene.entities[0].components.len(), 1);
assert!(scene.entities[0].components[0].represents::<ComponentA>());
assert_eq!(dynamic_world.entities.len(), 1);
assert_eq!(dynamic_world.entities[0].entity, entity);
assert_eq!(dynamic_world.entities[0].components.len(), 1);
assert!(dynamic_world.entities[0].components[0].represents::<ComponentA>());
}
#[test]
@@ -473,15 +473,15 @@ mod tests {
let entity = world.spawn((ComponentA, ComponentB)).id();
let scene = DynamicSceneBuilder::from_world(&world, &type_registry)
let dynamic_world = DynamicWorldBuilder::from_world(&world, &type_registry)
.extract_entity(entity)
.extract_entity(entity)
.build();
assert_eq!(scene.entities.len(), 1);
assert_eq!(scene.entities[0].entity, entity);
assert_eq!(scene.entities[0].components.len(), 1);
assert!(scene.entities[0].components[0].represents::<ComponentA>());
assert_eq!(dynamic_world.entities.len(), 1);
assert_eq!(dynamic_world.entities[0].entity, entity);
assert_eq!(dynamic_world.entities[0].components.len(), 1);
assert!(dynamic_world.entities[0].components[0].represents::<ComponentA>());
}
#[test]
@@ -494,15 +494,15 @@ mod tests {
let entity = world.spawn((ComponentA, ComponentB)).id();
let scene = DynamicSceneBuilder::from_world(&world, &type_registry)
let dynamic_world = DynamicWorldBuilder::from_world(&world, &type_registry)
.extract_entity(entity)
.build();
assert_eq!(scene.entities.len(), 1);
assert_eq!(scene.entities[0].entity, entity);
assert_eq!(scene.entities[0].components.len(), 2);
assert!(scene.entities[0].components[0].represents::<ComponentA>());
assert!(scene.entities[0].components[1].represents::<ComponentB>());
assert_eq!(dynamic_world.entities.len(), 1);
assert_eq!(dynamic_world.entities[0].entity, entity);
assert_eq!(dynamic_world.entities[0].components.len(), 2);
assert!(dynamic_world.entities[0].components[0].represents::<ComponentA>());
assert!(dynamic_world.entities[0].components[1].represents::<ComponentB>());
}
#[test]
@@ -517,7 +517,7 @@ mod tests {
// Insert entities out of order
let type_registry = TypeRegistry::default();
let builder = DynamicSceneBuilder::from_world(&world, &type_registry)
let builder = DynamicWorldBuilder::from_world(&world, &type_registry)
.extract_entity(entity_b)
.extract_entities([entity_d, entity_a].into_iter())
.extract_entity(entity_c);
@@ -544,14 +544,17 @@ mod tests {
let _entity_b = world.spawn(ComponentB).id();
let mut query = world.query_filtered::<Entity, With<ComponentA>>();
let scene = DynamicSceneBuilder::from_world(&world, &type_registry)
let dynamic_world = DynamicWorldBuilder::from_world(&world, &type_registry)
.extract_entities(query.iter(&world))
.build();
assert_eq!(scene.entities.len(), 2);
let mut scene_entities = vec![scene.entities[0].entity, scene.entities[1].entity];
scene_entities.sort();
assert_eq!(scene_entities, [entity_a, entity_a_b]);
assert_eq!(dynamic_world.entities.len(), 2);
let mut dynamic_world_entities = vec![
dynamic_world.entities[0].entity,
dynamic_world.entities[1].entity,
];
dynamic_world_entities.sort();
assert_eq!(dynamic_world_entities, [entity_a, entity_a_b]);
}
#[test]
@@ -564,13 +567,13 @@ mod tests {
let entity_a = world.spawn(ComponentA).id();
let entity_b = world.spawn(ComponentB).id();
let scene = DynamicSceneBuilder::from_world(&world, &type_registry)
let dynamic_world = DynamicWorldBuilder::from_world(&world, &type_registry)
.extract_entities([entity_a, entity_b].into_iter())
.remove_empty_entities()
.build();
assert_eq!(scene.entities.len(), 1);
assert_eq!(scene.entities[0].entity, entity_a);
assert_eq!(dynamic_world.entities.len(), 1);
assert_eq!(dynamic_world.entities[0].entity, entity_a);
}
#[test]
@@ -582,12 +585,12 @@ mod tests {
world.insert_resource(ResourceA);
let scene = DynamicSceneBuilder::from_world(&world, &type_registry)
let dynamic_world = DynamicWorldBuilder::from_world(&world, &type_registry)
.extract_resources()
.build();
assert_eq!(scene.resources.len(), 1);
assert!(scene.resources[0].represents::<ResourceA>());
assert_eq!(dynamic_world.resources.len(), 1);
assert!(dynamic_world.resources[0].represents::<ResourceA>());
}
#[test]
@@ -599,13 +602,13 @@ mod tests {
world.insert_resource(ResourceA);
let scene = DynamicSceneBuilder::from_world(&world, &type_registry)
let dynamic_world = DynamicWorldBuilder::from_world(&world, &type_registry)
.extract_resources()
.extract_resources()
.build();
assert_eq!(scene.resources.len(), 1);
assert!(scene.resources[0].represents::<ResourceA>());
assert_eq!(dynamic_world.resources.len(), 1);
assert!(dynamic_world.resources[0].represents::<ResourceA>());
}
#[test]
@@ -620,15 +623,15 @@ mod tests {
let entity_a = world.spawn(ComponentA).id();
let entity_b = world.spawn(ComponentB).id();
let scene = DynamicSceneBuilder::from_world(&world, &type_registry)
let dynamic_world = DynamicWorldBuilder::from_world(&world, &type_registry)
.allow_component::<ComponentA>()
.extract_entities([entity_a_b, entity_a, entity_b].into_iter())
.build();
assert_eq!(scene.entities.len(), 3);
assert!(scene.entities[2].components[0].represents::<ComponentA>());
assert!(scene.entities[1].components[0].represents::<ComponentA>());
assert_eq!(scene.entities[0].components.len(), 0);
assert_eq!(dynamic_world.entities.len(), 3);
assert!(dynamic_world.entities[2].components[0].represents::<ComponentA>());
assert!(dynamic_world.entities[1].components[0].represents::<ComponentA>());
assert_eq!(dynamic_world.entities[0].components.len(), 0);
}
#[test]
@@ -643,15 +646,15 @@ mod tests {
let entity_a = world.spawn(ComponentA).id();
let entity_b = world.spawn(ComponentB).id();
let scene = DynamicSceneBuilder::from_world(&world, &type_registry)
let dynamic_world = DynamicWorldBuilder::from_world(&world, &type_registry)
.deny_component::<ComponentA>()
.extract_entities([entity_a_b, entity_a, entity_b].into_iter())
.build();
assert_eq!(scene.entities.len(), 3);
assert!(scene.entities[0].components[0].represents::<ComponentB>());
assert_eq!(scene.entities[1].components.len(), 0);
assert!(scene.entities[2].components[0].represents::<ComponentB>());
assert_eq!(dynamic_world.entities.len(), 3);
assert!(dynamic_world.entities[0].components[0].represents::<ComponentB>());
assert_eq!(dynamic_world.entities[1].components.len(), 0);
assert!(dynamic_world.entities[2].components[0].represents::<ComponentB>());
}
#[test]
@@ -665,13 +668,13 @@ mod tests {
world.insert_resource(ResourceA);
world.insert_resource(ResourceB);
let scene = DynamicSceneBuilder::from_world(&world, &type_registry)
let dynamic_world = DynamicWorldBuilder::from_world(&world, &type_registry)
.allow_resource::<ResourceA>()
.extract_resources()
.build();
assert_eq!(scene.resources.len(), 1);
assert!(scene.resources[0].represents::<ResourceA>());
assert_eq!(dynamic_world.resources.len(), 1);
assert!(dynamic_world.resources[0].represents::<ResourceA>());
}
#[test]
@@ -685,13 +688,13 @@ mod tests {
world.insert_resource(ResourceA);
world.insert_resource(ResourceB);
let scene = DynamicSceneBuilder::from_world(&world, &type_registry)
let dynamic_world = DynamicWorldBuilder::from_world(&world, &type_registry)
.deny_resource::<ResourceA>()
.extract_resources()
.build();
assert_eq!(scene.resources.len(), 1);
assert!(scene.resources[0].represents::<ResourceB>());
assert_eq!(dynamic_world.resources.len(), 1);
assert!(dynamic_world.resources[0].represents::<ResourceB>());
}
#[test]
@@ -712,18 +715,18 @@ mod tests {
world.insert_resource(SomeResource(123));
let entity = world.spawn(SomeType(123)).id();
let scene = DynamicSceneBuilder::from_world(&world, &type_registry)
let dynamic_world = DynamicWorldBuilder::from_world(&world, &type_registry)
.extract_resources()
.extract_entities(vec![entity].into_iter())
.build();
let component = &scene.entities[0].components[0];
let component = &dynamic_world.entities[0].components[0];
assert!(component
.try_as_reflect()
.expect("component should be concrete due to `FromReflect`")
.is::<SomeType>());
let resource = &scene.resources[0];
let resource = &dynamic_world.resources[0];
assert!(resource
.try_as_reflect()
.expect("resource should be concrete due to `FromReflect`")
@@ -4,42 +4,42 @@
html_favicon_url = "https://bevy.org/assets/icon.png"
)]
//! Provides scene definition, instantiation and serialization/deserialization.
//! Provides dynamic world definition, instantiation, and serialization/deserialization.
//!
//! Scenes are collections of entities and their associated components that can be
//! instantiated or removed from a world to allow composition. Scenes can be serialized/deserialized,
//! [`DynamicWorld`]s are collections of entities and their associated components that can be
//! instantiated or removed from a world to allow composition. [`DynamicWorld`]s can be serialized/deserialized,
//! for example to save part of the world state to a file.
extern crate alloc;
mod components;
mod dynamic_scene;
mod dynamic_scene_builder;
mod dynamic_world;
mod dynamic_world_builder;
mod reflect_utils;
mod scene;
mod scene_filter;
mod scene_loader;
mod scene_spawner;
mod world_asset;
mod world_asset_loader;
mod world_asset_spawner;
mod world_filter;
#[cfg(feature = "serialize")]
pub mod serde;
pub use components::*;
pub use dynamic_scene::*;
pub use dynamic_scene_builder::*;
pub use scene::*;
pub use scene_filter::*;
pub use scene_loader::*;
pub use scene_spawner::*;
pub use dynamic_world::*;
pub use dynamic_world_builder::*;
pub use world_asset::*;
pub use world_asset_loader::*;
pub use world_asset_spawner::*;
pub use world_filter::*;
/// The scene prelude.
/// The `bevy_world_serialization` prelude.
///
/// This includes the most common types in this crate, re-exported for your convenience.
pub mod prelude {
#[doc(hidden)]
pub use crate::{
DynamicScene, DynamicSceneBuilder, DynamicSceneRoot, Scene, SceneFilter, SceneRoot,
SceneSpawner,
DynamicWorld, DynamicWorldBuilder, DynamicWorldRoot, WorldAsset, WorldAssetRoot,
WorldFilter, WorldInstanceSpawner,
};
}
@@ -50,70 +50,76 @@ use {
bevy_app::SceneSpawnerSystems, bevy_asset::AssetApp, bevy_ecs::schedule::IntoScheduleConfigs,
};
/// Plugin that provides scene functionality to an [`App`].
/// Plugin that provides world serialization functionality to an [`App`].
#[derive(Default)]
pub struct ScenePlugin;
pub struct WorldSerializationPlugin;
#[cfg(feature = "serialize")]
impl Plugin for ScenePlugin {
impl Plugin for WorldSerializationPlugin {
fn build(&self, app: &mut App) {
app.init_asset::<DynamicScene>()
.init_asset::<Scene>()
.init_asset_loader::<SceneLoader>()
.init_resource::<SceneSpawner>()
app.init_asset::<DynamicWorld>()
.init_asset::<WorldAsset>()
.init_asset_loader::<WorldAssetLoader>()
.init_resource::<WorldInstanceSpawner>()
.add_systems(
SpawnScene,
(scene_spawner, scene_spawner_system)
(world_instance_spawner, world_instance_spawner_system)
.chain()
.in_set(SceneSpawnerSystems::SceneSpawn),
.in_set(SceneSpawnerSystems::WorldInstanceSpawn),
);
// Register component hooks for DynamicSceneRoot
// Register component hooks for DynamicWorldRoot
app.world_mut()
.register_component_hooks::<DynamicSceneRoot>()
.register_component_hooks::<DynamicWorldRoot>()
.on_remove(|mut world, context| {
let Some(handle) = world.get::<DynamicSceneRoot>(context.entity) else {
let Some(handle) = world.get::<DynamicWorldRoot>(context.entity) else {
return;
};
let id = handle.id();
if let Some(&SceneInstance(scene_instance)) =
world.get::<SceneInstance>(context.entity)
if let Some(&WorldInstance(instance_id)) =
world.get::<WorldInstance>(context.entity)
{
let Some(mut scene_spawner) = world.get_resource_mut::<SceneSpawner>() else {
let Some(mut world_instance_spawner) =
world.get_resource_mut::<WorldInstanceSpawner>()
else {
return;
};
if let Some(instance_ids) = scene_spawner.spawned_dynamic_scenes.get_mut(&id) {
instance_ids.remove(&scene_instance);
if let Some(instance_ids) =
world_instance_spawner.spawned_dynamic_worlds.get_mut(&id)
{
instance_ids.remove(&instance_id);
}
scene_spawner.unregister_instance(scene_instance);
world_instance_spawner.unregister_instance(instance_id);
}
});
// Register component hooks for SceneRoot
// Register component hooks for WorldAssetRoot
app.world_mut()
.register_component_hooks::<SceneRoot>()
.register_component_hooks::<WorldAssetRoot>()
.on_remove(|mut world, context| {
let Some(handle) = world.get::<SceneRoot>(context.entity) else {
let Some(handle) = world.get::<WorldAssetRoot>(context.entity) else {
return;
};
let id = handle.id();
if let Some(&SceneInstance(scene_instance)) =
world.get::<SceneInstance>(context.entity)
if let Some(&WorldInstance(instance_id)) =
world.get::<WorldInstance>(context.entity)
{
let Some(mut scene_spawner) = world.get_resource_mut::<SceneSpawner>() else {
let Some(mut world_instance_spawner) =
world.get_resource_mut::<WorldInstanceSpawner>()
else {
return;
};
if let Some(instance_ids) = scene_spawner.spawned_scenes.get_mut(&id) {
instance_ids.remove(&scene_instance);
if let Some(instance_ids) = world_instance_spawner.spawned_worlds.get_mut(&id) {
instance_ids.remove(&instance_id);
}
scene_spawner.unregister_instance(scene_instance);
world_instance_spawner.unregister_instance(instance_id);
}
});
}
}
#[cfg(not(feature = "serialize"))]
impl Plugin for ScenePlugin {
impl Plugin for WorldSerializationPlugin {
fn build(&self, _: &mut App) {}
}
@@ -131,7 +137,8 @@ mod tests {
use bevy_reflect::Reflect;
use crate::{
DynamicScene, DynamicSceneBuilder, DynamicSceneRoot, Scene, ScenePlugin, SceneRoot,
DynamicWorld, DynamicWorldBuilder, DynamicWorldRoot, WorldAsset, WorldAssetRoot,
WorldSerializationPlugin,
};
#[derive(Component, Reflect, PartialEq, Debug)]
@@ -159,10 +166,10 @@ mod tests {
struct FinishLine;
#[test]
fn scene_spawns_and_respawns_after_change() {
fn world_instance_spawns_and_respawns_after_change() {
let mut app = App::new();
app.add_plugins((AssetPlugin::default(), ScenePlugin))
app.add_plugins((AssetPlugin::default(), WorldSerializationPlugin))
.register_type::<ChildOf>()
.register_type::<Children>()
.register_type::<Circle>()
@@ -170,21 +177,25 @@ mod tests {
.register_type::<Triangle>()
.register_type::<FinishLine>();
let scene_handle = app
let handle = app
.world_mut()
.resource_mut::<Assets<Scene>>()
.resource_mut::<Assets<WorldAsset>>()
.reserve_handle();
let scene_entity = app.world_mut().spawn(SceneRoot(scene_handle.clone())).id();
let instance_entity = app.world_mut().spawn(WorldAssetRoot(handle.clone())).id();
app.update();
assert!(app.world().entity(scene_entity).get::<Children>().is_none());
assert!(app
.world()
.entity(instance_entity)
.get::<Children>()
.is_none());
let mut scene_1 = Scene {
let mut world_1 = WorldAsset {
world: World::new(),
};
let root = scene_1.world.spawn_empty().id();
scene_1.world.spawn((
let root = world_1.world.spawn_empty().id();
world_1.world.spawn((
Rectangle {
width: 10.0,
height: 5.0,
@@ -192,30 +203,30 @@ mod tests {
FinishLine,
ChildOf(root),
));
scene_1.world.spawn((Circle { radius: 7.0 }, ChildOf(root)));
world_1.world.spawn((Circle { radius: 7.0 }, ChildOf(root)));
app.world_mut()
.resource_mut::<Assets<Scene>>()
.insert(&scene_handle, scene_1)
.resource_mut::<Assets<WorldAsset>>()
.insert(&handle, world_1)
.unwrap();
app.update();
// TODO: multiple updates to avoid debounced asset events. See comment on SceneSpawner::debounced_scene_asset_events
// TODO: multiple updates to avoid debounced asset events. See comment on WorldInstanceSpawner::debounced_world_asset_events
app.update();
app.update();
app.update();
let child_root = app
.world()
.entity(scene_entity)
.entity(instance_entity)
.get::<Children>()
.and_then(|children| children.first().cloned())
.expect("There should be exactly one child on the scene root");
.expect("There should be exactly one child on the world asset root");
let children = app
.world()
.entity(child_root)
.get::<Children>()
.expect("The child of the scene root should itself have 2 children");
.expect("The child of the world asset root should itself have 2 children");
assert_eq!(children.len(), 2);
let finish_line = app.world().entity(children[0]);
@@ -237,14 +248,14 @@ mod tests {
assert_eq!(circle, &Circle { radius: 7.0 });
assert_eq!(child_of.0, child_root);
// Now that we know our scene contains exactly what we expect, we will change the scene
// asset and ensure it contains the new scene results.
// Now that we know our world contains exactly what we expect, we will change the world
// asset and ensure it contains the new results.
let mut scene_2 = Scene {
let mut world_2 = WorldAsset {
world: World::new(),
};
let root = scene_2.world.spawn_empty().id();
scene_2.world.spawn((
let root = world_2.world.spawn_empty().id();
world_2.world.spawn((
Triangle {
base: 1.0,
height: 2.0,
@@ -253,8 +264,8 @@ mod tests {
));
app.world_mut()
.resource_mut::<Assets<Scene>>()
.insert(&scene_handle, scene_2)
.resource_mut::<Assets<WorldAsset>>()
.insert(&handle, world_2)
.unwrap();
app.update();
@@ -262,15 +273,15 @@ mod tests {
let child_root = app
.world()
.entity(scene_entity)
.entity(instance_entity)
.get::<Children>()
.and_then(|children| children.first().cloned())
.expect("There should be exactly one child on the scene root");
.expect("There should be exactly one child on the world asset root");
let children = app
.world()
.entity(child_root)
.get::<Children>()
.expect("The child of the scene root should itself have 2 children");
.expect("The child of the world asset root should itself have 2 children");
assert_eq!(children.len(), 1);
let triangle = app.world().entity(children[0]);
@@ -287,10 +298,10 @@ mod tests {
}
#[test]
fn dynamic_scene_spawns_and_respawns_after_change() {
fn dynamic_world_spawns_and_respawns_after_change() {
let mut app = App::new();
app.add_plugins((AssetPlugin::default(), ScenePlugin))
app.add_plugins((AssetPlugin::default(), WorldSerializationPlugin))
.register_type::<ChildOf>()
.register_type::<Children>()
.register_type::<Circle>()
@@ -298,32 +309,37 @@ mod tests {
.register_type::<Triangle>()
.register_type::<FinishLine>();
let scene_handle = app
let handle = app
.world_mut()
.resource_mut::<Assets<DynamicScene>>()
.resource_mut::<Assets<DynamicWorld>>()
.reserve_handle();
let scene_entity = app
.world_mut()
.spawn(DynamicSceneRoot(scene_handle.clone()))
.id();
let instance_entity = app.world_mut().spawn(DynamicWorldRoot(handle.clone())).id();
app.update();
assert!(app.world().entity(scene_entity).get::<Children>().is_none());
assert!(app
.world()
.entity(instance_entity)
.get::<Children>()
.is_none());
let create_dynamic_scene = |mut scene: Scene, world: &World| {
let create_dynamic_world = |mut world_asset: WorldAsset, world: &World| {
let type_registry = world.resource::<AppTypeRegistry>().read();
let entities: Vec<Entity> = scene.world.query::<Entity>().iter(&scene.world).collect();
DynamicSceneBuilder::from_world(&scene.world, &type_registry)
let entities: Vec<Entity> = world_asset
.world
.query::<Entity>()
.iter(&world_asset.world)
.collect();
DynamicWorldBuilder::from_world(&world_asset.world, &type_registry)
.extract_entities(entities.into_iter())
.build()
};
let mut scene_1 = Scene {
let mut world_1 = WorldAsset {
world: World::new(),
};
let root = scene_1.world.spawn_empty().id();
scene_1.world.spawn((
let root = world_1.world.spawn_empty().id();
world_1.world.spawn((
Rectangle {
width: 10.0,
height: 5.0,
@@ -331,31 +347,31 @@ mod tests {
FinishLine,
ChildOf(root),
));
scene_1.world.spawn((Circle { radius: 7.0 }, ChildOf(root)));
world_1.world.spawn((Circle { radius: 7.0 }, ChildOf(root)));
let scene_1 = create_dynamic_scene(scene_1, app.world());
let dynamic_world_1 = create_dynamic_world(world_1, app.world());
app.world_mut()
.resource_mut::<Assets<DynamicScene>>()
.insert(&scene_handle, scene_1)
.resource_mut::<Assets<DynamicWorld>>()
.insert(&handle, dynamic_world_1)
.unwrap();
app.update();
// TODO: multiple updates to avoid debounced asset events. See comment on SceneSpawner::debounced_scene_asset_events
// TODO: multiple updates to avoid debounced asset events. See comment on WorldInstanceSpawner::debounced_world_asset_events
app.update();
app.update();
app.update();
let child_root = app
.world()
.entity(scene_entity)
.entity(instance_entity)
.get::<Children>()
.and_then(|children| children.first().cloned())
.expect("There should be exactly one child on the scene root");
.expect("There should be exactly one child on the world asset root");
let children = app
.world()
.entity(child_root)
.get::<Children>()
.expect("The child of the scene root should itself have 2 children");
.expect("The child of the world asset root should itself have 2 children");
assert_eq!(children.len(), 2);
let finish_line = app.world().entity(children[0]);
@@ -377,14 +393,14 @@ mod tests {
assert_eq!(circle, &Circle { radius: 7.0 });
assert_eq!(child_of.0, child_root);
// Now that we know our scene contains exactly what we expect, we will change the scene
// asset and ensure it contains the new scene results.
// Now that we know our world contains exactly what we expect, we will change the world
// asset and ensure it contains the new results.
let mut scene_2 = Scene {
let mut world_2 = WorldAsset {
world: World::new(),
};
let root = scene_2.world.spawn_empty().id();
scene_2.world.spawn((
let root = world_2.world.spawn_empty().id();
world_2.world.spawn((
Triangle {
base: 1.0,
height: 2.0,
@@ -392,11 +408,11 @@ mod tests {
ChildOf(root),
));
let scene_2 = create_dynamic_scene(scene_2, app.world());
let dynamic_world_2 = create_dynamic_world(world_2, app.world());
app.world_mut()
.resource_mut::<Assets<DynamicScene>>()
.insert(&scene_handle, scene_2)
.resource_mut::<Assets<DynamicWorld>>()
.insert(&handle, dynamic_world_2)
.unwrap();
app.update();
@@ -404,15 +420,15 @@ mod tests {
let child_root = app
.world()
.entity(scene_entity)
.entity(instance_entity)
.get::<Children>()
.and_then(|children| children.first().cloned())
.expect("There should be exactly one child on the scene root");
.expect("There should be exactly one child on the world asset root");
let children = app
.world()
.entity(child_root)
.get::<Children>()
.expect("The child of the scene root should itself have 2 children");
.expect("The child of the world asset root should itself have 2 children");
assert_eq!(children.len(), 1);
let triangle = app.world().entity(children[0]);
@@ -1,6 +1,6 @@
//! `serde` serialization and deserialization implementation for Bevy scenes.
//! `serde` serialization and deserialization implementation for Bevy worlds.
use crate::{DynamicEntity, DynamicScene};
use crate::{DynamicEntity, DynamicWorld};
use bevy_asset::{
EphemeralHandleBehavior, HandleDeserializeProcessor, HandleSerializeProcessor, LoadFromPath,
};
@@ -20,79 +20,79 @@ use serde::{
Deserialize, Deserializer, Serialize, Serializer,
};
/// Name of the serialized scene struct type.
pub const SCENE_STRUCT: &str = "Scene";
/// Name of the serialized resources field in a scene struct.
pub const SCENE_RESOURCES: &str = "resources";
/// Name of the serialized entities field in a scene struct.
pub const SCENE_ENTITIES: &str = "entities";
/// Name of the serialized world struct type.
pub const WORLD_STRUCT: &str = "World";
/// Name of the serialized resources field in a world struct.
pub const WORLD_RESOURCES: &str = "resources";
/// Name of the serialized entities field in a world struct.
pub const WORLD_ENTITIES: &str = "entities";
/// Name of the serialized entity struct type.
pub const ENTITY_STRUCT: &str = "Entity";
/// Name of the serialized component field in an entity struct.
pub const ENTITY_FIELD_COMPONENTS: &str = "components";
/// Serializer for a [`DynamicScene`].
/// Serializer for a [`DynamicWorld`].
///
/// Helper object defining Bevy's serialize format for a [`DynamicScene`] and implementing
/// Helper object defining Bevy's serialize format for a [`DynamicWorld`] and implementing
/// the [`Serialize`] trait for use with Serde.
///
/// # Example
///
/// ```
/// # use bevy_ecs::prelude::*;
/// # use bevy_ecs_serialization::{DynamicScene, serde::SceneSerializer};
/// # use bevy_world_serialization::{DynamicWorld, serde::DynamicWorldSerializer};
/// # let mut world = World::default();
/// # world.insert_resource(AppTypeRegistry::default());
/// // Get the type registry
/// let registry = world.resource::<AppTypeRegistry>();
/// let registry = registry.read();
///
/// // Get a DynamicScene to serialize, for example from the World itself
/// let scene = DynamicScene::from_world(&world);
/// // Get a DynamicWorld to serialize, for example from the World itself
/// let dynamic_world = DynamicWorld::from_world(&world);
///
/// // Create a serializer for that DynamicScene, using the associated TypeRegistry
/// let scene_serializer = SceneSerializer::new(&scene, &registry);
/// // Create a serializer for that DynamicWorld, using the associated TypeRegistry
/// let serializer = DynamicWorldSerializer::new(&dynamic_world, &registry);
///
/// // Serialize through any serde-compatible Serializer
/// let ron_string = ron::ser::to_string(&scene_serializer);
/// let ron_string = ron::ser::to_string(&serializer);
/// ```
pub struct SceneSerializer<'a> {
/// The scene to serialize.
pub scene: &'a DynamicScene,
/// The type registry containing the types present in the scene.
pub struct DynamicWorldSerializer<'a> {
/// The dynamic world to serialize.
pub world: &'a DynamicWorld,
/// The type registry containing the types present in the dynamic world.
pub registry: &'a TypeRegistry,
}
impl<'a> SceneSerializer<'a> {
/// Create a new serializer from a [`DynamicScene`] and an associated [`TypeRegistry`].
impl<'a> DynamicWorldSerializer<'a> {
/// Create a new serializer from a [`DynamicWorld`] and an associated [`TypeRegistry`].
///
/// The type registry must contain all types present in the scene. This is generally the case
/// if you obtain both the scene and the registry from the same [`World`].
/// The type registry must contain all types present in the [`DynamicWorld`]. This is generally the case
/// if you obtain both the [`DynamicWorld`] and the registry from the same [`World`].
///
/// [`World`]: bevy_ecs::world::World
pub fn new(scene: &'a DynamicScene, registry: &'a TypeRegistry) -> Self {
SceneSerializer { scene, registry }
pub fn new(world: &'a DynamicWorld, registry: &'a TypeRegistry) -> Self {
DynamicWorldSerializer { world, registry }
}
}
impl<'a> Serialize for SceneSerializer<'a> {
impl<'a> Serialize for DynamicWorldSerializer<'a> {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
let mut state = serializer.serialize_struct(SCENE_STRUCT, 2)?;
let mut state = serializer.serialize_struct(WORLD_STRUCT, 2)?;
state.serialize_field(
SCENE_RESOURCES,
&SceneMapSerializer {
entries: &self.scene.resources,
WORLD_RESOURCES,
&WorldMapSerializer {
entries: &self.world.resources,
registry: self.registry,
},
)?;
state.serialize_field(
SCENE_ENTITIES,
WORLD_ENTITIES,
&EntitiesSerializer {
entities: &self.scene.entities,
entities: &self.world.entities,
registry: self.registry,
},
)?;
@@ -143,7 +143,7 @@ impl<'a> Serialize for EntitySerializer<'a> {
let mut state = serializer.serialize_struct(ENTITY_STRUCT, 1)?;
state.serialize_field(
ENTITY_FIELD_COMPONENTS,
&SceneMapSerializer {
&WorldMapSerializer {
entries: &self.entity.components,
registry: self.registry,
},
@@ -154,19 +154,19 @@ impl<'a> Serialize for EntitySerializer<'a> {
/// Handles serializing a list of values with a unique type as a map of type to value.
///
/// Used to serialize scene resources in [`SceneSerializer`] and entity components in [`EntitySerializer`].
/// Used to serialize world resources in [`DynamicWorldSerializer`] and entity components in [`EntitySerializer`].
/// Note that having several entries of the same type in `entries` will lead to an error when using the RON format and
/// deserializing through [`SceneMapDeserializer`].
/// deserializing through [`WorldMapDeserializer`].
///
/// Note: The entries are sorted by type path before they're serialized.
pub struct SceneMapSerializer<'a> {
pub struct WorldMapSerializer<'a> {
/// List of boxed values of unique type to serialize.
pub entries: &'a [Box<dyn PartialReflect>],
/// Type registry in which the types used in `entries` are registered.
pub registry: &'a TypeRegistry,
}
impl<'a> Serialize for SceneMapSerializer<'a> {
impl<'a> Serialize for WorldMapSerializer<'a> {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
@@ -206,7 +206,7 @@ impl<'a> Serialize for SceneMapSerializer<'a> {
#[derive(Deserialize)]
#[serde(field_identifier, rename_all = "lowercase")]
enum SceneField {
enum WorldField {
Resources,
Entities,
}
@@ -217,25 +217,25 @@ enum EntityField {
Components,
}
/// Handles scene deserialization.
pub struct SceneDeserializer<'a> {
/// Type registry in which the components and resources types used in the scene to deserialize are registered.
/// Handles world deserialization.
pub struct WorldDeserializer<'a> {
/// Type registry in which the components and resources types used in the world to deserialize are registered.
pub type_registry: &'a TypeRegistry,
/// The [`LoadFromPath`] implementation allowing us to deserialize asset handles.
pub load_from_path: &'a mut dyn LoadFromPath,
}
impl<'a, 'de> DeserializeSeed<'de> for SceneDeserializer<'a> {
type Value = DynamicScene;
impl<'a, 'de> DeserializeSeed<'de> for WorldDeserializer<'a> {
type Value = DynamicWorld;
fn deserialize<D>(self, deserializer: D) -> Result<Self::Value, D::Error>
where
D: Deserializer<'de>,
{
deserializer.deserialize_struct(
SCENE_STRUCT,
&[SCENE_RESOURCES, SCENE_ENTITIES],
SceneVisitor {
WORLD_STRUCT,
&[WORLD_RESOURCES, WORLD_ENTITIES],
WorldVisitor {
type_registry: self.type_registry,
load_from_path: self.load_from_path,
},
@@ -243,16 +243,16 @@ impl<'a, 'de> DeserializeSeed<'de> for SceneDeserializer<'a> {
}
}
struct SceneVisitor<'a> {
struct WorldVisitor<'a> {
type_registry: &'a TypeRegistry,
load_from_path: &'a mut dyn LoadFromPath,
}
impl<'a, 'de> Visitor<'de> for SceneVisitor<'a> {
type Value = DynamicScene;
impl<'a, 'de> Visitor<'de> for WorldVisitor<'a> {
type Value = DynamicWorld;
fn expecting(&self, formatter: &mut Formatter) -> core::fmt::Result {
formatter.write_str("scene struct")
formatter.write_str("world struct")
}
fn visit_seq<A>(self, mut seq: A) -> Result<Self::Value, A::Error>
@@ -260,20 +260,20 @@ impl<'a, 'de> Visitor<'de> for SceneVisitor<'a> {
A: SeqAccess<'de>,
{
let resources = seq
.next_element_seed(SceneMapDeserializer {
.next_element_seed(WorldMapDeserializer {
registry: self.type_registry,
load_from_path: self.load_from_path,
})?
.ok_or_else(|| Error::missing_field(SCENE_RESOURCES))?;
.ok_or_else(|| Error::missing_field(WORLD_RESOURCES))?;
let entities = seq
.next_element_seed(SceneEntitiesDeserializer {
.next_element_seed(WorldEntitiesDeserializer {
type_registry: self.type_registry,
load_from_path: self.load_from_path,
})?
.ok_or_else(|| Error::missing_field(SCENE_ENTITIES))?;
.ok_or_else(|| Error::missing_field(WORLD_ENTITIES))?;
Ok(DynamicScene {
Ok(DynamicWorld {
resources,
entities,
})
@@ -287,20 +287,20 @@ impl<'a, 'de> Visitor<'de> for SceneVisitor<'a> {
let mut entities = None;
while let Some(key) = map.next_key()? {
match key {
SceneField::Resources => {
WorldField::Resources => {
if resources.is_some() {
return Err(Error::duplicate_field(SCENE_RESOURCES));
return Err(Error::duplicate_field(WORLD_RESOURCES));
}
resources = Some(map.next_value_seed(SceneMapDeserializer {
resources = Some(map.next_value_seed(WorldMapDeserializer {
registry: self.type_registry,
load_from_path: self.load_from_path,
})?);
}
SceneField::Entities => {
WorldField::Entities => {
if entities.is_some() {
return Err(Error::duplicate_field(SCENE_ENTITIES));
return Err(Error::duplicate_field(WORLD_ENTITIES));
}
entities = Some(map.next_value_seed(SceneEntitiesDeserializer {
entities = Some(map.next_value_seed(WorldEntitiesDeserializer {
type_registry: self.type_registry,
load_from_path: self.load_from_path,
})?);
@@ -308,10 +308,10 @@ impl<'a, 'de> Visitor<'de> for SceneVisitor<'a> {
}
}
let resources = resources.ok_or_else(|| Error::missing_field(SCENE_RESOURCES))?;
let entities = entities.ok_or_else(|| Error::missing_field(SCENE_ENTITIES))?;
let resources = resources.ok_or_else(|| Error::missing_field(WORLD_RESOURCES))?;
let entities = entities.ok_or_else(|| Error::missing_field(WORLD_ENTITIES))?;
Ok(DynamicScene {
Ok(DynamicWorld {
resources,
entities,
})
@@ -319,33 +319,33 @@ impl<'a, 'de> Visitor<'de> for SceneVisitor<'a> {
}
/// Handles deserialization for a collection of entities.
pub struct SceneEntitiesDeserializer<'a> {
pub struct WorldEntitiesDeserializer<'a> {
/// Type registry in which the component types used by the entities to deserialize are registered.
pub type_registry: &'a TypeRegistry,
/// The [`LoadFromPath`] implementation allowing us to deserialize asset handles.
pub load_from_path: &'a mut dyn LoadFromPath,
}
impl<'a, 'de> DeserializeSeed<'de> for SceneEntitiesDeserializer<'a> {
impl<'a, 'de> DeserializeSeed<'de> for WorldEntitiesDeserializer<'a> {
type Value = Vec<DynamicEntity>;
fn deserialize<D>(self, deserializer: D) -> Result<Self::Value, D::Error>
where
D: Deserializer<'de>,
{
deserializer.deserialize_map(SceneEntitiesVisitor {
deserializer.deserialize_map(WorldEntitiesVisitor {
type_registry: self.type_registry,
load_from_path: self.load_from_path,
})
}
}
struct SceneEntitiesVisitor<'a> {
struct WorldEntitiesVisitor<'a> {
type_registry: &'a TypeRegistry,
load_from_path: &'a mut dyn LoadFromPath,
}
impl<'a, 'de> Visitor<'de> for SceneEntitiesVisitor<'a> {
impl<'a, 'de> Visitor<'de> for WorldEntitiesVisitor<'a> {
type Value = Vec<DynamicEntity>;
fn expecting(&self, formatter: &mut Formatter) -> core::fmt::Result {
@@ -358,7 +358,7 @@ impl<'a, 'de> Visitor<'de> for SceneEntitiesVisitor<'a> {
{
let mut entities = Vec::new();
while let Some(entity) = map.next_key::<Entity>()? {
let entity = map.next_value_seed(SceneEntityDeserializer {
let entity = map.next_value_seed(WorldEntityDeserializer {
entity,
type_registry: self.type_registry,
load_from_path: self.load_from_path,
@@ -371,7 +371,7 @@ impl<'a, 'de> Visitor<'de> for SceneEntitiesVisitor<'a> {
}
/// Handle deserialization of an entity and its components.
pub struct SceneEntityDeserializer<'a> {
pub struct WorldEntityDeserializer<'a> {
/// Id of the deserialized entity.
pub entity: Entity,
/// Type registry in which the component types used by the entity to deserialize are registered.
@@ -380,7 +380,7 @@ pub struct SceneEntityDeserializer<'a> {
pub load_from_path: &'a mut dyn LoadFromPath,
}
impl<'a, 'de> DeserializeSeed<'de> for SceneEntityDeserializer<'a> {
impl<'a, 'de> DeserializeSeed<'de> for WorldEntityDeserializer<'a> {
type Value = DynamicEntity;
fn deserialize<D>(self, deserializer: D) -> Result<Self::Value, D::Error>
@@ -390,7 +390,7 @@ impl<'a, 'de> DeserializeSeed<'de> for SceneEntityDeserializer<'a> {
deserializer.deserialize_struct(
ENTITY_STRUCT,
&[ENTITY_FIELD_COMPONENTS],
SceneEntityVisitor {
WorldEntityVisitor {
entity: self.entity,
registry: self.type_registry,
load_from_path: self.load_from_path,
@@ -399,13 +399,13 @@ impl<'a, 'de> DeserializeSeed<'de> for SceneEntityDeserializer<'a> {
}
}
struct SceneEntityVisitor<'a> {
struct WorldEntityVisitor<'a> {
entity: Entity,
registry: &'a TypeRegistry,
load_from_path: &'a mut dyn LoadFromPath,
}
impl<'a, 'de> Visitor<'de> for SceneEntityVisitor<'a> {
impl<'a, 'de> Visitor<'de> for WorldEntityVisitor<'a> {
type Value = DynamicEntity;
fn expecting(&self, formatter: &mut Formatter) -> core::fmt::Result {
@@ -417,7 +417,7 @@ impl<'a, 'de> Visitor<'de> for SceneEntityVisitor<'a> {
A: SeqAccess<'de>,
{
let components = seq
.next_element_seed(SceneMapDeserializer {
.next_element_seed(WorldMapDeserializer {
registry: self.registry,
load_from_path: self.load_from_path,
})?
@@ -441,7 +441,7 @@ impl<'a, 'de> Visitor<'de> for SceneEntityVisitor<'a> {
return Err(Error::duplicate_field(ENTITY_FIELD_COMPONENTS));
}
components = Some(map.next_value_seed(SceneMapDeserializer {
components = Some(map.next_value_seed(WorldMapDeserializer {
registry: self.registry,
load_from_path: self.load_from_path,
})?);
@@ -460,33 +460,33 @@ impl<'a, 'de> Visitor<'de> for SceneEntityVisitor<'a> {
}
/// Handles deserialization of a sequence of values with unique types.
pub struct SceneMapDeserializer<'a> {
pub struct WorldMapDeserializer<'a> {
/// Type registry in which the types of the values to deserialize are registered.
pub registry: &'a TypeRegistry,
/// The [`LoadFromPath`] implementation allowing us to deserialize asset handles.
pub load_from_path: &'a mut dyn LoadFromPath,
}
impl<'a, 'de> DeserializeSeed<'de> for SceneMapDeserializer<'a> {
impl<'a, 'de> DeserializeSeed<'de> for WorldMapDeserializer<'a> {
type Value = Vec<Box<dyn PartialReflect>>;
fn deserialize<D>(self, deserializer: D) -> Result<Self::Value, D::Error>
where
D: Deserializer<'de>,
{
deserializer.deserialize_map(SceneMapVisitor {
deserializer.deserialize_map(WorldMapVisitor {
registry: self.registry,
load_from_path: self.load_from_path,
})
}
}
struct SceneMapVisitor<'a> {
struct WorldMapVisitor<'a> {
registry: &'a TypeRegistry,
load_from_path: &'a mut dyn LoadFromPath,
}
impl<'a, 'de> Visitor<'de> for SceneMapVisitor<'a> {
impl<'a, 'de> Visitor<'de> for WorldMapVisitor<'a> {
type Value = Vec<Box<dyn PartialReflect>>;
fn expecting(&self, formatter: &mut Formatter) -> core::fmt::Result {
@@ -548,8 +548,8 @@ impl<'a, 'de> Visitor<'de> for SceneMapVisitor<'a> {
#[cfg(test)]
mod tests {
use crate::{
serde::{SceneDeserializer, SceneSerializer},
DynamicScene, DynamicSceneBuilder,
serde::{DynamicWorldSerializer, WorldDeserializer},
DynamicWorld, DynamicWorldBuilder,
};
use bevy_asset::{Asset, AssetPath, Handle, LoadFromPath, ReflectAsset, UntypedHandle};
use bevy_ecs::{
@@ -679,9 +679,9 @@ mod tests {
world.insert_resource(MyResource { foo: 123 });
let scene = {
let dynamic_world = {
let type_registry = world.resource::<AppTypeRegistry>().read();
DynamicSceneBuilder::from_world(&world, &type_registry)
DynamicWorldBuilder::from_world(&world, &type_registry)
.extract_entities([a, b, c, d].into_iter())
.extract_resources()
.build()
@@ -689,43 +689,43 @@ mod tests {
let expected = r#"(
resources: {
"bevy_ecs_serialization::serde::tests::MyResource": (
"bevy_world_serialization::serde::tests::MyResource": (
foo: 123,
),
},
entities: {
4294967290: (
components: {
"bevy_ecs_serialization::serde::tests::FakeMesh3d": (Uuid("00000000-0000-0000-0000-000000000001")),
"bevy_world_serialization::serde::tests::FakeMesh3d": (Uuid("00000000-0000-0000-0000-000000000001")),
},
),
4294967291: (
components: {
"bevy_ecs_serialization::serde::tests::Bar": (345),
"bevy_ecs_serialization::serde::tests::Baz": (789),
"bevy_ecs_serialization::serde::tests::Foo": (123),
"bevy_world_serialization::serde::tests::Bar": (345),
"bevy_world_serialization::serde::tests::Baz": (789),
"bevy_world_serialization::serde::tests::Foo": (123),
},
),
4294967292: (
components: {
"bevy_ecs_serialization::serde::tests::Bar": (345),
"bevy_ecs_serialization::serde::tests::Foo": (123),
"bevy_world_serialization::serde::tests::Bar": (345),
"bevy_world_serialization::serde::tests::Foo": (123),
},
),
4294967293: (
components: {
"bevy_ecs_serialization::serde::tests::Foo": (123),
"bevy_world_serialization::serde::tests::Foo": (123),
},
),
},
)"#;
let output = scene
let output = dynamic_world
.serialize(&world.resource::<AppTypeRegistry>().read())
.unwrap();
assert_eq!(expected, output);
}
/// A fake handle creator for the purposes of testing scene loading.
/// A fake handle creator for the purposes of testing world loading.
struct FakeHandleCreator;
impl LoadFromPath for FakeHandleCreator {
@@ -744,57 +744,59 @@ mod tests {
let input = r#"(
resources: {
"bevy_ecs_serialization::serde::tests::MyResource": (
"bevy_world_serialization::serde::tests::MyResource": (
foo: 123,
),
},
entities: {
8589934591: (
components: {
"bevy_ecs_serialization::serde::tests::Foo": (123),
"bevy_world_serialization::serde::tests::Foo": (123),
},
),
8589934590: (
components: {
"bevy_ecs_serialization::serde::tests::Foo": (123),
"bevy_ecs_serialization::serde::tests::Bar": (345),
"bevy_world_serialization::serde::tests::Foo": (123),
"bevy_world_serialization::serde::tests::Bar": (345),
},
),
8589934589: (
components: {
"bevy_ecs_serialization::serde::tests::Foo": (123),
"bevy_ecs_serialization::serde::tests::Bar": (345),
"bevy_ecs_serialization::serde::tests::Baz": (789),
"bevy_world_serialization::serde::tests::Foo": (123),
"bevy_world_serialization::serde::tests::Bar": (345),
"bevy_world_serialization::serde::tests::Baz": (789),
},
),
8589934588: (
components: {
"bevy_ecs_serialization::serde::tests::FakeMesh3d": (Uuid("00000000-0000-0000-0000-000000000001")),
"bevy_world_serialization::serde::tests::FakeMesh3d": (Uuid("00000000-0000-0000-0000-000000000001")),
},
),
},
)"#;
let mut deserializer = ron::de::Deserializer::from_str(input).unwrap();
let scene_deserializer = SceneDeserializer {
let world_deserializer = WorldDeserializer {
type_registry: &world.resource::<AppTypeRegistry>().read(),
load_from_path: &mut FakeHandleCreator,
};
let scene = scene_deserializer.deserialize(&mut deserializer).unwrap();
let dynamic_world = world_deserializer.deserialize(&mut deserializer).unwrap();
assert_eq!(
1,
scene.resources.len(),
dynamic_world.resources.len(),
"expected `resources` to contain 1 resource"
);
assert_eq!(
4,
scene.entities.len(),
dynamic_world.entities.len(),
"expected `entities` to contain 3 entities"
);
let mut map = EntityHashMap::default();
let mut dst_world = create_world();
scene.write_to_world(&mut dst_world, &mut map).unwrap();
dynamic_world
.write_to_world(&mut dst_world, &mut map)
.unwrap();
let my_resource = dst_world.get_resource::<MyResource>();
assert!(my_resource.is_some());
@@ -807,17 +809,17 @@ mod tests {
assert_eq!(1, dst_world.query::<&FakeMesh3d>().iter(&dst_world).count());
}
fn roundtrip_ron(world: &World) -> (DynamicScene, DynamicScene) {
let scene = DynamicScene::from_world(world);
fn roundtrip_ron(world: &World) -> (DynamicWorld, DynamicWorld) {
let dynamic_world = DynamicWorld::from_world(world);
let registry = world.resource::<AppTypeRegistry>().read();
let serialized = scene.serialize(&registry).unwrap();
let serialized = dynamic_world.serialize(&registry).unwrap();
let mut deserializer = ron::de::Deserializer::from_str(&serialized).unwrap();
let scene_deserializer = SceneDeserializer {
let world_deserializer = WorldDeserializer {
type_registry: &registry,
load_from_path: &mut FakeHandleCreator,
};
let deserialized_scene = scene_deserializer.deserialize(&mut deserializer).unwrap();
(scene, deserialized_scene)
let deserialized_world = world_deserializer.deserialize(&mut deserializer).unwrap();
(dynamic_world, deserialized_world)
}
#[test]
@@ -831,16 +833,16 @@ mod tests {
world.despawn(a);
world.spawn(MyEntityRef(foo)).insert(Bar(123));
let (scene, deserialized_scene) = roundtrip_ron(&world);
let (input_world, deserialized_world) = roundtrip_ron(&world);
let mut map = EntityHashMap::default();
let mut dst_world = create_world();
deserialized_scene
deserialized_world
.write_to_world(&mut dst_world, &mut map)
.unwrap();
assert_eq!(2, deserialized_scene.entities.len());
assert_scene_eq(&scene, &deserialized_scene);
assert_eq!(2, deserialized_world.entities.len());
assert_world_eq(&input_world, &deserialized_world);
let bar_to_foo = dst_world
.query_filtered::<&MyEntityRef, Without<Foo>>()
@@ -865,13 +867,13 @@ mod tests {
let qux = Qux(42);
world.spawn(qux);
let (scene, deserialized_scene) = roundtrip_ron(&world);
let (input_world, deserialized_world) = roundtrip_ron(&world);
assert_eq!(1, deserialized_scene.entities.len());
assert_scene_eq(&scene, &deserialized_scene);
assert_eq!(1, deserialized_world.entities.len());
assert_world_eq(&input_world, &deserialized_world);
let mut world = create_world();
deserialized_scene
deserialized_world
.write_to_world(&mut world, &mut EntityHashMap::default())
.unwrap();
assert_eq!(&qux, world.query::<&Qux>().single(&world).unwrap());
@@ -883,13 +885,13 @@ mod tests {
let fake_mesh = FakeMesh3d(Uuid::from_u128(1).into());
world.spawn(fake_mesh.clone());
let (scene, deserialized_scene) = roundtrip_ron(&world);
let (input_world, deserialized_world) = roundtrip_ron(&world);
assert_eq!(1, deserialized_scene.entities.len());
assert_scene_eq(&scene, &deserialized_scene);
assert_eq!(1, deserialized_world.entities.len());
assert_world_eq(&input_world, &deserialized_world);
let mut world = create_world();
deserialized_scene
deserialized_world
.write_to_world(&mut world, &mut EntityHashMap::default())
.unwrap();
assert_eq!(
@@ -911,32 +913,32 @@ mod tests {
let registry = world.resource::<AppTypeRegistry>();
let registry = &registry.read();
let scene = DynamicScene::from_world(&world);
let dynamic_world = DynamicWorld::from_world(&world);
let scene_serializer = SceneSerializer::new(&scene, registry);
let serialized_scene = postcard::to_allocvec(&scene_serializer).unwrap();
let dynamic_world_serializer = DynamicWorldSerializer::new(&dynamic_world, registry);
let serialized_world = postcard::to_allocvec(&dynamic_world_serializer).unwrap();
assert_eq!(
vec![
0, 1, 253, 255, 255, 255, 15, 1, 49, 98, 101, 118, 121, 95, 101, 99, 115, 95, 115,
101, 114, 105, 97, 108, 105, 122, 97, 116, 105, 111, 110, 58, 58, 115, 101, 114,
100, 101, 58, 58, 116, 101, 115, 116, 115, 58, 58, 77, 121, 67, 111, 109, 112, 111,
110, 101, 110, 116, 1, 2, 3, 102, 102, 166, 63, 205, 204, 108, 64, 1, 12, 72, 101,
108, 108, 111, 32, 87, 111, 114, 108, 100, 33
0, 1, 253, 255, 255, 255, 15, 1, 51, 98, 101, 118, 121, 95, 119, 111, 114, 108,
100, 95, 115, 101, 114, 105, 97, 108, 105, 122, 97, 116, 105, 111, 110, 58, 58,
115, 101, 114, 100, 101, 58, 58, 116, 101, 115, 116, 115, 58, 58, 77, 121, 67, 111,
109, 112, 111, 110, 101, 110, 116, 1, 2, 3, 102, 102, 166, 63, 205, 204, 108, 64,
1, 12, 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33
],
serialized_scene
serialized_world
);
let scene_deserializer = SceneDeserializer {
let world_deserializer = WorldDeserializer {
type_registry: registry,
load_from_path: &mut FakeHandleCreator,
};
let deserialized_scene = scene_deserializer
.deserialize(&mut postcard::Deserializer::from_bytes(&serialized_scene))
let deserialized_world = world_deserializer
.deserialize(&mut postcard::Deserializer::from_bytes(&serialized_world))
.unwrap();
assert_eq!(1, deserialized_scene.entities.len());
assert_scene_eq(&scene, &deserialized_scene);
assert_eq!(1, deserialized_world.entities.len());
assert_world_eq(&dynamic_world, &deserialized_world);
}
#[test]
@@ -952,41 +954,41 @@ mod tests {
let registry = world.resource::<AppTypeRegistry>();
let registry = &registry.read();
let scene = DynamicScene::from_world(&world);
let dynamic_world = DynamicWorld::from_world(&world);
let scene_serializer = SceneSerializer::new(&scene, registry);
let dynamic_world_serializer = DynamicWorldSerializer::new(&dynamic_world, registry);
let mut buf = Vec::new();
let mut ser = rmp_serde::Serializer::new(&mut buf);
scene_serializer.serialize(&mut ser).unwrap();
dynamic_world_serializer.serialize(&mut ser).unwrap();
assert_eq!(
vec![
146, 128, 129, 206, 255, 255, 255, 253, 145, 129, 217, 49, 98, 101, 118, 121, 95,
101, 99, 115, 95, 115, 101, 114, 105, 97, 108, 105, 122, 97, 116, 105, 111, 110,
58, 58, 115, 101, 114, 100, 101, 58, 58, 116, 101, 115, 116, 115, 58, 58, 77, 121,
67, 111, 109, 112, 111, 110, 101, 110, 116, 147, 147, 1, 2, 3, 146, 202, 63, 166,
102, 102, 202, 64, 108, 204, 205, 129, 165, 84, 117, 112, 108, 101, 172, 72, 101,
108, 108, 111, 32, 87, 111, 114, 108, 100, 33
146, 128, 129, 206, 255, 255, 255, 253, 145, 129, 217, 51, 98, 101, 118, 121, 95,
119, 111, 114, 108, 100, 95, 115, 101, 114, 105, 97, 108, 105, 122, 97, 116, 105,
111, 110, 58, 58, 115, 101, 114, 100, 101, 58, 58, 116, 101, 115, 116, 115, 58, 58,
77, 121, 67, 111, 109, 112, 111, 110, 101, 110, 116, 147, 147, 1, 2, 3, 146, 202,
63, 166, 102, 102, 202, 64, 108, 204, 205, 129, 165, 84, 117, 112, 108, 101, 172,
72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33
],
buf
);
let scene_deserializer = SceneDeserializer {
let world_deserializer = WorldDeserializer {
type_registry: registry,
load_from_path: &mut FakeHandleCreator,
};
let mut reader = BufReader::new(buf.as_slice());
let deserialized_scene = scene_deserializer
let deserialized_world = world_deserializer
.deserialize(&mut rmp_serde::Deserializer::new(&mut reader))
.unwrap();
assert_eq!(1, deserialized_scene.entities.len());
assert_scene_eq(&scene, &deserialized_scene);
assert_eq!(1, deserialized_world.entities.len());
assert_world_eq(&dynamic_world, &deserialized_world);
}
/// A crude equality checker for [`DynamicScene`], used solely for testing purposes.
fn assert_scene_eq(expected: &DynamicScene, received: &DynamicScene) {
/// A crude equality checker for [`DynamicWorld`], used solely for testing purposes.
fn assert_world_eq(expected: &DynamicWorld, received: &DynamicWorld) {
assert_eq!(
expected.entities.len(),
received.entities.len(),
@@ -1027,15 +1029,15 @@ mod tests {
}
}
/// These tests just verify that the [`assert_scene_eq`] function is working properly for our tests.
mod assert_scene_eq_tests {
/// These tests just verify that the [`assert_world_eq`] function is working properly for our tests.
mod assert_world_eq_tests {
use super::*;
#[test]
#[should_panic(expected = "entity count did not match")]
fn should_panic_when_entity_count_not_eq() {
let mut world = create_world();
let scene_a = DynamicScene::from_world(&world);
let a = DynamicWorld::from_world(&world);
world.spawn(MyComponent {
foo: [1, 2, 3],
@@ -1043,9 +1045,9 @@ mod tests {
baz: MyEnum::Unit,
});
let scene_b = DynamicScene::from_world(&world);
let b = DynamicWorld::from_world(&world);
assert_scene_eq(&scene_a, &scene_b);
assert_world_eq(&a, &b);
}
#[test]
@@ -1061,7 +1063,7 @@ mod tests {
})
.id();
let scene_a = DynamicScene::from_world(&world);
let a = DynamicWorld::from_world(&world);
world.entity_mut(entity).insert(MyComponent {
foo: [3, 2, 1],
@@ -1069,9 +1071,9 @@ mod tests {
baz: MyEnum::Unit,
});
let scene_b = DynamicScene::from_world(&world);
let b = DynamicWorld::from_world(&world);
assert_scene_eq(&scene_a, &scene_b);
assert_world_eq(&a, &b);
}
#[test]
@@ -1087,13 +1089,13 @@ mod tests {
})
.id();
let scene_a = DynamicScene::from_world(&world);
let a = DynamicWorld::from_world(&world);
world.entity_mut(entity).remove::<MyComponent>();
let scene_b = DynamicScene::from_world(&world);
let b = DynamicWorld::from_world(&world);
assert_scene_eq(&scene_a, &scene_b);
assert_world_eq(&a, &b);
}
}
}
@@ -1,7 +1,7 @@
use core::any::TypeId;
use crate::reflect_utils::clone_reflect_value;
use crate::{DynamicScene, SceneSpawnError};
use crate::{DynamicWorld, WorldInstanceSpawnError};
use bevy_asset::Asset;
use bevy_ecs::resource::IS_RESOURCE;
use bevy_ecs::{
@@ -16,38 +16,41 @@ use bevy_reflect::{TypePath, TypeRegistry};
/// A composition of [`World`] objects.
///
/// To spawn a scene, you can use either:
/// * [`SceneSpawner::spawn`](crate::SceneSpawner::spawn)
/// * adding the [`SceneRoot`](crate::components::SceneRoot) component to an entity.
/// To spawn a [`WorldAsset`], you can use either:
/// * [`WorldInstanceSpawner::spawn`](crate::WorldInstanceSpawner::spawn)
/// * adding the [`WorldAssetRoot`](crate::components::WorldAssetRoot) component to an entity.
#[derive(Asset, TypePath, Debug)]
pub struct Scene {
/// The world of the scene, containing its entities and resources.
pub struct WorldAsset {
/// The world, containing its entities and resources.
pub world: World,
}
impl Scene {
/// Creates a new scene with the given world.
impl WorldAsset {
/// Creates a new [`WorldAsset`] with the given world.
pub fn new(world: World) -> Self {
Self { world }
}
/// Create a new scene from a given dynamic scene.
pub fn from_dynamic_scene(
dynamic_scene: &DynamicScene,
/// Create a new [`WorldAsset`] from a given dynamic world.
pub fn from_dynamic_world(
dynamic_world: &DynamicWorld,
type_registry: &TypeRegistry,
) -> Result<Scene, SceneSpawnError> {
) -> Result<WorldAsset, WorldInstanceSpawnError> {
let mut world = World::new();
let mut entity_map = EntityHashMap::default();
dynamic_scene.write_to_world_with(&mut world, &mut entity_map, type_registry)?;
dynamic_world.write_to_world_with(&mut world, &mut entity_map, type_registry)?;
Ok(Self { world })
}
/// Clone the scene.
/// Clone the [`WorldAsset`].
///
/// This method will return a [`SceneSpawnError`] if a type either is not registered in the
/// This method will return a [`WorldInstanceSpawnError`] if a type either is not registered in the
/// provided [`AppTypeRegistry`] or doesn't reflect the [`Component`](bevy_ecs::component::Component) trait.
pub fn clone_with(&self, type_registry: &AppTypeRegistry) -> Result<Scene, SceneSpawnError> {
pub fn clone_with(
&self,
type_registry: &AppTypeRegistry,
) -> Result<WorldAsset, WorldInstanceSpawnError> {
let mut new_world = World::new();
let mut entity_map = EntityHashMap::default();
self.write_to_world_with(&mut new_world, &mut entity_map, type_registry)?;
@@ -56,14 +59,14 @@ impl Scene {
/// Write the entities and their corresponding components to the given world.
///
/// This method will return a [`SceneSpawnError`] if a type either is not registered in the
/// This method will return a [`WorldInstanceSpawnError`] if a type either is not registered in the
/// provided [`AppTypeRegistry`] or doesn't reflect the [`Component`](bevy_ecs::component::Component) trait.
pub fn write_to_world_with(
&self,
world: &mut World,
entity_map: &mut EntityHashMap<Entity>,
type_registry: &AppTypeRegistry,
) -> Result<(), SceneSpawnError> {
) -> Result<(), WorldInstanceSpawnError> {
let type_registry = type_registry.read();
let self_dqf_id = self
@@ -94,14 +97,13 @@ impl Scene {
.type_id()
.expect("reflected resources must have a type_id");
let registration =
type_registry
.get(type_id)
.ok_or_else(|| SceneSpawnError::UnregisteredType {
std_type_name: component_info.name(),
})?;
let registration = type_registry.get(type_id).ok_or_else(|| {
WorldInstanceSpawnError::UnregisteredType {
std_type_name: component_info.name(),
}
})?;
registration.data::<ReflectResource>().ok_or_else(|| {
SceneSpawnError::UnregisteredResource {
WorldInstanceSpawnError::UnregisteredResource {
type_path: registration.type_info().type_path().to_string(),
}
})?;
@@ -127,7 +129,7 @@ impl Scene {
);
}
// Ensure that all scene entities have been allocated in the destination
// Ensure that all source world entities have been allocated in the destination
// world before handling components that may contain references that need mapping.
for archetype in self.world.archetypes().iter() {
if archetype.contains(IS_RESOURCE) {
@@ -165,12 +167,12 @@ impl Scene {
let registration = type_registry
.get(component_info.type_id().unwrap())
.ok_or_else(|| SceneSpawnError::UnregisteredType {
.ok_or_else(|| WorldInstanceSpawnError::UnregisteredType {
std_type_name: component_info.name(),
})?;
let reflect_component =
registration.data::<ReflectComponent>().ok_or_else(|| {
SceneSpawnError::UnregisteredComponent {
WorldInstanceSpawnError::UnregisteredComponent {
type_path: registration.type_info().type_path().to_string(),
}
})?;
@@ -184,8 +186,8 @@ impl Scene {
continue;
};
// If this component references entities in the scene,
// update them to the entities in the world.
// If this component references entities in the source world,
// update them to the entities in the destination world.
SceneEntityMapper::world_scope(entity_map, world, |world, mapper| {
reflect_component.apply_or_insert_mapped(
&mut world.entity_mut(entity),
@@ -6,17 +6,17 @@ use bevy_reflect::{TypePath, TypeRegistryArc};
#[cfg(feature = "serialize")]
use {
crate::{serde::SceneDeserializer, DynamicScene},
crate::{serde::WorldDeserializer, DynamicWorld},
bevy_asset::{io::Reader, AssetLoader, LoadContext},
serde::de::DeserializeSeed,
thiserror::Error,
};
/// Asset loader for a Bevy dynamic scene (`.scn` / `.scn.ron`).
/// Asset loader for a Bevy dynamic world (`.scn` / `.scn.ron`).
///
/// The loader handles assets serialized with [`DynamicScene::serialize`].
/// The loader handles assets serialized with [`DynamicWorld::serialize`].
#[derive(Debug, TypePath)]
pub struct SceneLoader {
pub struct WorldAssetLoader {
#[cfg_attr(
not(feature = "serialize"),
expect(dead_code, reason = "only used with `serialize` feature")
@@ -24,22 +24,22 @@ pub struct SceneLoader {
type_registry: TypeRegistryArc,
}
impl FromWorld for SceneLoader {
impl FromWorld for WorldAssetLoader {
fn from_world(world: &mut World) -> Self {
let type_registry = world.resource::<AppTypeRegistry>();
SceneLoader {
WorldAssetLoader {
type_registry: type_registry.0.clone(),
}
}
}
/// Possible errors that can be produced by [`SceneLoader`]
/// Possible errors that can be produced by [`WorldAssetLoader`]
#[cfg(feature = "serialize")]
#[non_exhaustive]
#[derive(Debug, Error)]
pub enum SceneLoaderError {
pub enum WorldAssetLoaderError {
/// An [IO Error](std::io::Error)
#[error("Error while trying to read the scene file: {0}")]
#[error("Error while trying to read the world file: {0}")]
Io(#[from] std::io::Error),
/// A [RON Error](ron::error::SpannedError)
#[error("Could not parse RON: {0}")]
@@ -47,10 +47,10 @@ pub enum SceneLoaderError {
}
#[cfg(feature = "serialize")]
impl AssetLoader for SceneLoader {
type Asset = DynamicScene;
impl AssetLoader for WorldAssetLoader {
type Asset = DynamicWorld;
type Settings = ();
type Error = SceneLoaderError;
type Error = WorldAssetLoaderError;
async fn load(
&self,
@@ -61,7 +61,7 @@ impl AssetLoader for SceneLoader {
let mut bytes = Vec::new();
reader.read_to_end(&mut bytes).await?;
let mut deserializer = ron::de::Deserializer::from_bytes(&bytes)?;
let scene_deserializer = SceneDeserializer {
let scene_deserializer = WorldDeserializer {
type_registry: &self.type_registry.read(),
load_from_path: load_context,
};
File diff suppressed because it is too large Load Diff
@@ -1,17 +1,17 @@
use bevy_platform::collections::{hash_set::IntoIter, HashSet};
use core::any::{Any, TypeId};
/// A filter used to control which types can be added to a [`DynamicScene`].
/// A filter used to control which types can be added to a [`DynamicWorld`].
///
/// This scene filter _can_ be used more generically to represent a filter for any given type;
/// however, note that its intended usage with `DynamicScene` only considers [components] and [resources].
/// Adding types that are not a component or resource will have no effect when used with `DynamicScene`.
/// This world filter _can_ be used more generically to represent a filter for any given type;
/// however, note that its intended usage with `DynamicWorld` only considers [components] and [resources].
/// Adding types that are not a component or resource will have no effect when used with `DynamicWorld`.
///
/// [`DynamicScene`]: crate::DynamicScene
/// [`DynamicWorld`]: crate::DynamicWorld
/// [components]: bevy_ecs::prelude::Component
/// [resources]: bevy_ecs::prelude::Resource
#[derive(Default, Debug, Clone, PartialEq, Eq)]
pub enum SceneFilter {
pub enum WorldFilter {
/// Represents an unset filter.
///
/// This is the equivalent of an empty [`Denylist`] or an [`Allowlist`] containing every type—
@@ -20,32 +20,32 @@ pub enum SceneFilter {
/// [Allowing] a type will convert this filter to an `Allowlist`.
/// Similarly, [denying] a type will convert this filter to a `Denylist`.
///
/// [`Denylist`]: SceneFilter::Denylist
/// [`Allowlist`]: SceneFilter::Allowlist
/// [Allowing]: SceneFilter::allow
/// [denying]: SceneFilter::deny
/// [`Denylist`]: WorldFilter::Denylist
/// [`Allowlist`]: WorldFilter::Allowlist
/// [Allowing]: WorldFilter::allow
/// [denying]: WorldFilter::deny
#[default]
Unset,
/// Contains the set of permitted types by their [`TypeId`].
///
/// Types not contained within this set should not be allowed to be saved to an associated [`DynamicScene`].
/// Types not contained within this set should not be allowed to be saved to an associated [`DynamicWorld`].
///
/// [`DynamicScene`]: crate::DynamicScene
/// [`DynamicWorld`]: crate::DynamicWorld
Allowlist(HashSet<TypeId>),
/// Contains the set of prohibited types by their [`TypeId`].
///
/// Types contained within this set should not be allowed to be saved to an associated [`DynamicScene`].
/// Types contained within this set should not be allowed to be saved to an associated [`DynamicWorld`].
///
/// [`DynamicScene`]: crate::DynamicScene
/// [`DynamicWorld`]: crate::DynamicWorld
Denylist(HashSet<TypeId>),
}
impl SceneFilter {
impl WorldFilter {
/// Creates a filter where all types are allowed.
///
/// This is the equivalent of creating an empty [`Denylist`].
///
/// [`Denylist`]: SceneFilter::Denylist
/// [`Denylist`]: WorldFilter::Denylist
pub fn allow_all() -> Self {
Self::Denylist(HashSet::default())
}
@@ -54,7 +54,7 @@ impl SceneFilter {
///
/// This is the equivalent of creating an empty [`Allowlist`].
///
/// [`Allowlist`]: SceneFilter::Allowlist
/// [`Allowlist`]: WorldFilter::Allowlist
pub fn deny_all() -> Self {
Self::Allowlist(HashSet::default())
}
@@ -66,9 +66,9 @@ impl SceneFilter {
///
/// If this filter is [`Unset`], then it will be completely replaced by a new [`Allowlist`].
///
/// [`Denylist`]: SceneFilter::Denylist
/// [`Unset`]: SceneFilter::Unset
/// [`Allowlist`]: SceneFilter::Allowlist
/// [`Denylist`]: WorldFilter::Denylist
/// [`Unset`]: WorldFilter::Unset
/// [`Allowlist`]: WorldFilter::Allowlist
#[must_use]
pub fn allow<T: Any>(self) -> Self {
self.allow_by_id(TypeId::of::<T>())
@@ -81,9 +81,9 @@ impl SceneFilter {
///
/// If this filter is [`Unset`], then it will be completely replaced by a new [`Allowlist`].
///
/// [`Denylist`]: SceneFilter::Denylist
/// [`Unset`]: SceneFilter::Unset
/// [`Allowlist`]: SceneFilter::Allowlist
/// [`Denylist`]: WorldFilter::Denylist
/// [`Unset`]: WorldFilter::Unset
/// [`Allowlist`]: WorldFilter::Allowlist
#[must_use]
pub fn allow_by_id(mut self, type_id: TypeId) -> Self {
match &mut self {
@@ -107,9 +107,9 @@ impl SceneFilter {
///
/// If this filter is [`Unset`], then it will be completely replaced by a new [`Denylist`].
///
/// [`Allowlist`]: SceneFilter::Allowlist
/// [`Unset`]: SceneFilter::Unset
/// [`Denylist`]: SceneFilter::Denylist
/// [`Allowlist`]: WorldFilter::Allowlist
/// [`Unset`]: WorldFilter::Unset
/// [`Denylist`]: WorldFilter::Denylist
#[must_use]
pub fn deny<T: Any>(self) -> Self {
self.deny_by_id(TypeId::of::<T>())
@@ -122,9 +122,9 @@ impl SceneFilter {
///
/// If this filter is [`Unset`], then it will be completely replaced by a new [`Denylist`].
///
/// [`Allowlist`]: SceneFilter::Allowlist
/// [`Unset`]: SceneFilter::Unset
/// [`Denylist`]: SceneFilter::Denylist
/// [`Allowlist`]: WorldFilter::Allowlist
/// [`Unset`]: WorldFilter::Unset
/// [`Denylist`]: WorldFilter::Denylist
#[must_use]
pub fn deny_by_id(mut self, type_id: TypeId) -> Self {
match &mut self {
@@ -143,7 +143,7 @@ impl SceneFilter {
///
/// If the filter is [`Unset`], this will always return `true`.
///
/// [`Unset`]: SceneFilter::Unset
/// [`Unset`]: WorldFilter::Unset
pub fn is_allowed<T: Any>(&self) -> bool {
self.is_allowed_by_id(TypeId::of::<T>())
}
@@ -152,7 +152,7 @@ impl SceneFilter {
///
/// If the filter is [`Unset`], this will always return `true`.
///
/// [`Unset`]: SceneFilter::Unset
/// [`Unset`]: WorldFilter::Unset
pub fn is_allowed_by_id(&self, type_id: TypeId) -> bool {
match self {
Self::Unset => true,
@@ -165,7 +165,7 @@ impl SceneFilter {
///
/// If the filter is [`Unset`], this will always return `false`.
///
/// [`Unset`]: SceneFilter::Unset
/// [`Unset`]: WorldFilter::Unset
pub fn is_denied<T: Any>(&self) -> bool {
self.is_denied_by_id(TypeId::of::<T>())
}
@@ -174,7 +174,7 @@ impl SceneFilter {
///
/// If the filter is [`Unset`], this will always return `false`.
///
/// [`Unset`]: SceneFilter::Unset
/// [`Unset`]: WorldFilter::Unset
pub fn is_denied_by_id(&self, type_id: TypeId) -> bool {
!self.is_allowed_by_id(type_id)
}
@@ -183,7 +183,7 @@ impl SceneFilter {
///
/// If the filter is [`Unset`], this will return an empty iterator.
///
/// [`Unset`]: SceneFilter::Unset
/// [`Unset`]: WorldFilter::Unset
pub fn iter(&self) -> Box<dyn ExactSizeIterator<Item = &TypeId> + '_> {
match self {
Self::Unset => Box::new(core::iter::empty()),
@@ -195,7 +195,7 @@ impl SceneFilter {
///
/// If the filter is [`Unset`], this will always return a length of zero.
///
/// [`Unset`]: SceneFilter::Unset
/// [`Unset`]: WorldFilter::Unset
pub fn len(&self) -> usize {
match self {
Self::Unset => 0,
@@ -207,7 +207,7 @@ impl SceneFilter {
///
/// If the filter is [`Unset`], this will always return `true`.
///
/// [`Unset`]: SceneFilter::Unset
/// [`Unset`]: WorldFilter::Unset
pub fn is_empty(&self) -> bool {
match self {
Self::Unset => true,
@@ -216,7 +216,7 @@ impl SceneFilter {
}
}
impl IntoIterator for SceneFilter {
impl IntoIterator for WorldFilter {
type Item = TypeId;
type IntoIter = IntoIter<TypeId>;
@@ -234,21 +234,21 @@ mod tests {
#[test]
fn should_set_list_type_if_none() {
let filter = SceneFilter::Unset.allow::<i32>();
assert!(matches!(filter, SceneFilter::Allowlist(_)));
let filter = WorldFilter::Unset.allow::<i32>();
assert!(matches!(filter, WorldFilter::Allowlist(_)));
let filter = SceneFilter::Unset.deny::<i32>();
assert!(matches!(filter, SceneFilter::Denylist(_)));
let filter = WorldFilter::Unset.deny::<i32>();
assert!(matches!(filter, WorldFilter::Denylist(_)));
}
#[test]
fn should_add_to_list() {
let filter = SceneFilter::default().allow::<i16>().allow::<i32>();
let filter = WorldFilter::default().allow::<i16>().allow::<i32>();
assert_eq!(2, filter.len());
assert!(filter.is_allowed::<i16>());
assert!(filter.is_allowed::<i32>());
let filter = SceneFilter::default().deny::<i16>().deny::<i32>();
let filter = WorldFilter::default().deny::<i16>().deny::<i32>();
assert_eq!(2, filter.len());
assert!(filter.is_denied::<i16>());
assert!(filter.is_denied::<i32>());
@@ -256,7 +256,7 @@ mod tests {
#[test]
fn should_remove_from_list() {
let filter = SceneFilter::default()
let filter = WorldFilter::default()
.allow::<i16>()
.allow::<i32>()
.deny::<i32>();
@@ -264,7 +264,7 @@ mod tests {
assert!(filter.is_allowed::<i16>());
assert!(!filter.is_allowed::<i32>());
let filter = SceneFilter::default()
let filter = WorldFilter::default()
.deny::<i16>()
.deny::<i32>()
.allow::<i32>();
+2 -2
View File
@@ -39,7 +39,7 @@ collections to build your own "profile" equivalent, without needing to manually
|dev|Enable this feature during development to improve the development experience. This adds features like asset hot-reloading and debugging tools. This should not be enabled for published apps! **Feature set:** `debug`, `bevy_dev_tools`, `file_watcher`.|
|audio|Features used to build audio Bevy apps. **Feature set:** `bevy_audio`, `vorbis`.|
|audio-all-formats|Enables audio features and all supported formats. **Feature set:** `bevy_audio`, `aac`, `flac`, `mp3`, `mp4`, `vorbis`, `wav`.|
|scene|Features used to compose Bevy scenes. **Feature set:** `bevy_ecs_serialization`, `bevy_scene2`.|
|scene|Features used to compose Bevy scenes. **Feature set:** `bevy_world_serialization`, `bevy_scene2`.|
|picking|Enables picking with all backends. **Feature set:** `bevy_picking`, `mesh_picking`, `sprite_picking`, `ui_picking`.|
|default_app|The core pieces that most apps need. This serves as a baseline feature set for other higher level feature collections (such as "2d" and "3d"). It is also useful as a baseline feature set for scenarios like headless apps that require no rendering (ex: command line tools, servers, etc). **Feature set:** `async_executor`, `bevy_asset`, `bevy_input_focus`, `bevy_log`, `bevy_state`, `bevy_window`, `custom_cursor`, `reflect_auto_register`.|
|default_platform|These are platform support features, such as OS support/features, windowing and input backends, etc. **Feature set:** `std`, `android-game-activity`, `bevy_gilrs`, `bevy_winit`, `default_font`, `multi_threaded`, `webgl2`, `x11`, `wayland`, `sysinfo_plugin`.|
@@ -77,7 +77,6 @@ This is the complete `bevy` cargo feature list, without "profiles" or "collectio
|bevy_core_pipeline|Provides cameras and other basic render pipeline features|
|bevy_debug_stepping|Enable stepping-based debugging of Bevy systems|
|bevy_dev_tools|Provides a collection of developer tools|
|bevy_ecs_serialization|Provides ECS serialization functionality|
|bevy_gilrs|Adds gamepad support|
|bevy_gizmos|Adds support for gizmos|
|bevy_gizmos_render|Adds support for rendering gizmos|
@@ -108,6 +107,7 @@ This is the complete `bevy` cargo feature list, without "profiles" or "collectio
|bevy_ui_widgets|Headless widget collection for Bevy UI.|
|bevy_window|Windowing layer|
|bevy_winit|winit window and input backend|
|bevy_world_serialization|Provides ECS serialization functionality|
|bluenoise_texture|Include spatio-temporal blue noise KTX2 file used by generated environment maps, Solari and atmosphere|
|bmp|BMP image format support|
|compressed_image_saver|Enables compressed KTX2 UASTC texture output on the asset processor|
+3 -1
View File
@@ -106,7 +106,9 @@ fn setup(mut commands: Commands, asset_server: Res<AssetServer>, app_status: Res
spawn_directional_light(&mut commands);
commands.spawn((
SceneRoot(asset_server.load("models/AnisotropyBarnLamp/AnisotropyBarnLamp.gltf#Scene0")),
WorldAssetRoot(
asset_server.load("models/AnisotropyBarnLamp/AnisotropyBarnLamp.gltf#Scene0"),
),
Transform::from_xyz(0.0, 0.07, -0.13),
Scene::BarnLamp,
));
+1 -1
View File
@@ -437,7 +437,7 @@ fn setup(
}
// Flight Helmet
commands.spawn(SceneRoot(asset_server.load(
commands.spawn(WorldAssetRoot(asset_server.load(
GltfAssetLabel::Scene(0).from_asset("models/FlightHelmet/FlightHelmet.gltf"),
)));
+1 -1
View File
@@ -237,7 +237,7 @@ fn setup_terrain_scene(
// Terrain
commands.spawn((
Terrain,
SceneRoot(
WorldAssetRoot(
asset_server.load(GltfAssetLabel::Scene(0).from_asset("models/terrain/terrain.glb")),
),
Transform::from_xyz(-1.0, 0.0, -0.5)
+1 -1
View File
@@ -66,7 +66,7 @@ fn setup_terrain_scene(
));
// Terrain
commands.spawn(SceneRoot(asset_server.load(
commands.spawn(WorldAssetRoot(asset_server.load(
GltfAssetLabel::Scene(0).from_asset("models/terrain/Mountains.gltf"),
)));
+1 -1
View File
@@ -147,7 +147,7 @@ fn spawn_coated_glass_bubble_sphere(
/// extension.
fn spawn_golf_ball(commands: &mut Commands, asset_server: &AssetServer) {
commands.spawn((
SceneRoot(
WorldAssetRoot(
asset_server.load(GltfAssetLabel::Scene(0).from_asset("models/GolfBall/GolfBall.glb")),
),
Transform::from_xyz(1.0, 1.0, 0.0).with_scale(Vec3::splat(SPHERE_SCALE)),
+2 -2
View File
@@ -344,13 +344,13 @@ fn add_camera(commands: &mut Commands, asset_server: &AssetServer, color_grading
fn add_basic_scene(commands: &mut Commands, asset_server: &AssetServer) {
// Spawn the main scene.
commands.spawn(SceneRoot(asset_server.load(
commands.spawn(WorldAssetRoot(asset_server.load(
GltfAssetLabel::Scene(0).from_asset("models/TonemappingTest/TonemappingTest.gltf"),
)));
// Spawn the flight helmet.
commands.spawn((
SceneRoot(
WorldAssetRoot(
asset_server
.load(GltfAssetLabel::Scene(0).from_asset("models/FlightHelmet/FlightHelmet.gltf")),
),
+2 -2
View File
@@ -182,14 +182,14 @@ fn setup(mut commands: Commands, asset_server: Res<AssetServer>) {
commands
.spawn((
SceneRoot(asset_server.load(
WorldAssetRoot(asset_server.load(
GltfAssetLabel::Scene(0).from_asset("models/FlightHelmet/FlightHelmet.gltf"),
)),
Transform::from_rotation(Quat::from_rotation_y(std::f32::consts::PI)),
))
.observe(
|event: On<Pointer<Drag>>,
mut query: Query<&mut Transform, With<SceneRoot>>,
mut query: Query<&mut Transform, With<WorldAssetRoot>>,
mut commands: Commands,
mut window: Query<Entity, With<PrimaryWindow>>| {
for mut transform in query.iter_mut() {
+2 -2
View File
@@ -76,9 +76,9 @@ fn setup(
let helmet_scene = asset_server
.load(GltfAssetLabel::Scene(0).from_asset("models/FlightHelmet/FlightHelmet.gltf"));
commands.spawn(SceneRoot(helmet_scene.clone()));
commands.spawn(WorldAssetRoot(helmet_scene.clone()));
commands.spawn((
SceneRoot(helmet_scene),
WorldAssetRoot(helmet_scene),
Transform::from_xyz(-4.0, 0.0, -3.0),
));
+1 -1
View File
@@ -84,7 +84,7 @@ fn setup(mut commands: Commands, asset_server: Res<AssetServer>, app_settings: R
}
// Spawn the scene.
commands.spawn(SceneRoot(asset_server.load(
commands.spawn(WorldAssetRoot(asset_server.load(
GltfAssetLabel::Scene(0).from_asset("models/DepthOfFieldExample/DepthOfFieldExample.glb"),
)));
+9 -6
View File
@@ -89,7 +89,7 @@ enum ExampleModel {
#[derive(Resource)]
struct ExampleAssets {
// The glTF scene containing the colored floor.
main_scene: Handle<Scene>,
main_scene: Handle<WorldAsset>,
// The 3D texture containing the irradiance volume.
irradiance_volume: Handle<Image>,
@@ -101,7 +101,7 @@ struct ExampleAssets {
main_sphere_material: Handle<StandardMaterial>,
// The glTF scene containing the animated fox.
fox: Handle<Scene>,
fox: Handle<WorldAsset>,
// The graph containing the animation that the fox will play.
fox_animation_graph: Handle<AnimationGraph>,
@@ -228,7 +228,7 @@ fn setup(mut commands: Commands, assets: Res<ExampleAssets>, app_status: Res<App
}
fn spawn_main_scene(commands: &mut Commands, assets: &ExampleAssets) {
commands.spawn(SceneRoot(assets.main_scene.clone()));
commands.spawn(WorldAssetRoot(assets.main_scene.clone()));
}
fn spawn_camera(commands: &mut Commands, assets: &ExampleAssets) {
@@ -281,7 +281,7 @@ fn spawn_voxel_cube_parent(commands: &mut Commands) {
fn spawn_fox(commands: &mut Commands, assets: &ExampleAssets) {
commands.spawn((
SceneRoot(assets.fox.clone()),
WorldAssetRoot(assets.fox.clone()),
Visibility::Hidden,
Transform::from_scale(Vec3::splat(FOX_SCALE)),
MainObject,
@@ -369,8 +369,11 @@ fn rotate_camera(
fn change_main_object(
keyboard: Res<ButtonInput<KeyCode>>,
mut app_status: ResMut<AppStatus>,
mut sphere_query: Query<&mut Visibility, (With<MainObject>, With<Mesh3d>, Without<SceneRoot>)>,
mut fox_query: Query<&mut Visibility, (With<MainObject>, With<SceneRoot>)>,
mut sphere_query: Query<
&mut Visibility,
(With<MainObject>, With<Mesh3d>, Without<WorldAssetRoot>),
>,
mut fox_query: Query<&mut Visibility, (With<MainObject>, With<WorldAssetRoot>)>,
) {
if !keyboard.just_pressed(KeyCode::Tab) {
return;
+1 -1
View File
@@ -259,7 +259,7 @@ fn spawn_camera(commands: &mut Commands) {
/// Spawns the glTF scene that contains the two rooms.
fn spawn_gltf_scene(commands: &mut Commands, asset_server: &AssetServer) {
commands.spawn(SceneRoot(asset_server.load(
commands.spawn(WorldAssetRoot(asset_server.load(
GltfAssetLabel::Scene(0).from_asset(get_web_asset_url("two_rooms.glb")),
)));
}
+1 -1
View File
@@ -253,7 +253,7 @@ fn spawn_light_textures(
Transform::from_translation(Vec3::new(0.0, 1.8, 0.01)).with_scale(Vec3::splat(0.1)),
Selection::PointLight,
children![
SceneRoot(
WorldAssetRoot(
asset_server.load(GltfAssetLabel::Scene(0).from_asset("models/Faces/faces.glb")),
),
(
+1 -1
View File
@@ -40,7 +40,7 @@ fn main() {
}
fn setup(mut commands: Commands, asset_server: Res<AssetServer>, args: Res<Args>) {
commands.spawn(SceneRoot(asset_server.load(
commands.spawn(WorldAssetRoot(asset_server.load(
GltfAssetLabel::Scene(0).from_asset("models/CornellBox/CornellBox.glb"),
)));
+2 -2
View File
@@ -282,7 +282,7 @@ fn spawn_mirror_camera(
/// [`play_fox_animation`].
fn spawn_fox(commands: &mut Commands, asset_server: &AssetServer) {
commands.spawn((
SceneRoot(asset_server.load(GltfAssetLabel::Scene(0).from_asset(FOX_ASSET_PATH))),
WorldAssetRoot(asset_server.load(GltfAssetLabel::Scene(0).from_asset(FOX_ASSET_PATH))),
Transform::from_xyz(-50.0, 0.0, -100.0),
));
}
@@ -441,7 +441,7 @@ fn create_mirror_texture_image(images: &mut Assets<Image>, window_size: UVec2) -
// Moves the fox when the user moves the mouse with the left button down.
fn move_fox_on_mouse_down(
mut scene_roots_query: Query<&mut Transform, With<SceneRoot>>,
mut scene_roots_query: Query<&mut Transform, With<WorldAssetRoot>>,
windows_query: Query<&Window, With<PrimaryWindow>>,
cameras_query: Query<(&Camera, &GlobalTransform)>,
interactions_query: Query<&Interaction, With<RadioButton>>,
+3 -3
View File
@@ -5,7 +5,7 @@ use bevy::{
pbr::Lightmap,
picking::{backend::HitData, pointer::PointerInteraction},
prelude::*,
scene::SceneInstanceReady,
world_serialization::WorldInstanceReady,
};
use crate::widgets::{RadioButton, RadioButtonText, WidgetClickEvent, WidgetClickSender};
@@ -165,14 +165,14 @@ fn spawn_camera(commands: &mut Commands) {
/// The scene is loaded from a glTF file.
fn spawn_scene(commands: &mut Commands, asset_server: &AssetServer) {
commands
.spawn(SceneRoot(
.spawn(WorldAssetRoot(
asset_server.load(
GltfAssetLabel::Scene(0)
.from_asset("models/MixedLightingExample/MixedLightingExample.gltf"),
),
))
.observe(
|_: On<SceneInstanceReady>,
|_: On<WorldInstanceReady>,
mut lighting_mode_changed_writer: MessageWriter<LightingModeChanged>| {
// When the scene loads, send a `LightingModeChanged` event so
// that we set up the lightmaps.
+1 -1
View File
@@ -82,7 +82,7 @@ fn setup(
mut materials: ResMut<Assets<StandardMaterial>>,
) {
// Spawn the glTF scene.
commands.spawn(SceneRoot(asset_server.load(OUTER_CUBE_URL)));
commands.spawn(WorldAssetRoot(asset_server.load(OUTER_CUBE_URL)));
spawn_camera(&mut commands);
spawn_inner_cube(&mut commands, &mut meshes, &mut materials);
+1 -1
View File
@@ -209,7 +209,7 @@ fn spawn_light(commands: &mut Commands, app_status: &AppStatus) {
/// Loads and spawns the glTF palm tree scene.
fn spawn_gltf_scene(commands: &mut Commands, asset_server: &AssetServer) {
commands.spawn(SceneRoot(
commands.spawn(WorldAssetRoot(
asset_server.load("models/PalmTree/PalmTree.gltf#Scene0"),
));
}
+2 -2
View File
@@ -101,13 +101,13 @@ fn spawn_camera(commands: &mut Commands, asset_server: &AssetServer) {
/// variety of colors.
fn spawn_scene(commands: &mut Commands, asset_server: &AssetServer) {
// Spawn the main scene.
commands.spawn(SceneRoot(asset_server.load(
commands.spawn(WorldAssetRoot(asset_server.load(
GltfAssetLabel::Scene(0).from_asset("models/TonemappingTest/TonemappingTest.gltf"),
)));
// Spawn the flight helmet.
commands.spawn((
SceneRoot(
WorldAssetRoot(
asset_server
.load(GltfAssetLabel::Scene(0).from_asset("models/FlightHelmet/FlightHelmet.gltf")),
),
+3 -1
View File
@@ -111,7 +111,9 @@ fn setup(
// Spawns the cubes, light, and camera.
fn spawn_scene(commands: &mut Commands, asset_server: &AssetServer) {
commands.spawn((
SceneRoot(asset_server.load(GltfAssetLabel::Scene(0).from_asset("models/cubes/Cubes.glb"))),
WorldAssetRoot(
asset_server.load(GltfAssetLabel::Scene(0).from_asset("models/cubes/Cubes.glb")),
),
CubesScene,
));
}
+4 -4
View File
@@ -11,11 +11,11 @@ use bevy::{
post_process::bloom::Bloom,
prelude::*,
render::{diagnostic::RenderDiagnosticsPlugin, render_resource::TextureUsages},
scene::SceneInstanceReady,
solari::{
pathtracer::{Pathtracer, PathtracingPlugin},
prelude::{RaytracingMesh3d, SolariLighting, SolariPlugins},
},
world_serialization::WorldInstanceReady,
};
use chacha20::ChaCha8Rng;
use rand::{RngExt, SeedableRng};
@@ -84,7 +84,7 @@ fn setup_pica_pica(
) {
commands
.spawn((
SceneRoot(
WorldAssetRoot(
asset_server.load(
GltfAssetLabel::Scene(0)
.from_asset("https://github.com/bevyengine/bevy_asset_files/raw/2a5950295a8b6d9d051d59c0df69e87abcda58c3/pica_pica/mini_diorama_01.glb")
@@ -96,7 +96,7 @@ fn setup_pica_pica(
commands
.spawn((
SceneRoot(asset_server.load(
WorldAssetRoot(asset_server.load(
GltfAssetLabel::Scene(0).from_asset("https://github.com/bevyengine/bevy_asset_files/raw/2a5950295a8b6d9d051d59c0df69e87abcda58c3/pica_pica/robot_01.glb")
)),
Transform::from_scale(Vec3::splat(2.0))
@@ -370,7 +370,7 @@ fn setup_many_lights(
}
fn add_raytracing_meshes_on_scene_load(
scene_ready: On<SceneInstanceReady>,
scene_ready: On<WorldInstanceReady>,
children: Query<&Children>,
mesh_query: Query<(
&Mesh3d,
+1 -1
View File
@@ -27,7 +27,7 @@ fn setup(
MeshMaterial3d(materials.add(Color::srgb(0.3, 0.5, 0.3))),
));
commands.spawn(SceneRoot(
commands.spawn(WorldAssetRoot(
asset_server.load(GltfAssetLabel::Scene(0).from_asset("models/animated/Fox.glb")),
));
+1 -1
View File
@@ -277,7 +277,7 @@ fn spawn_cube(
// Spawns the flight helmet.
fn spawn_flight_helmet(commands: &mut Commands, asset_server: &AssetServer) {
commands.spawn((
SceneRoot(
WorldAssetRoot(
asset_server
.load(GltfAssetLabel::Scene(0).from_asset("models/FlightHelmet/FlightHelmet.gltf")),
),
+2 -2
View File
@@ -100,7 +100,7 @@ fn setup(
fn setup_basic_scene(mut commands: Commands, asset_server: Res<AssetServer>) {
// Main scene
commands.spawn((
SceneRoot(asset_server.load(
WorldAssetRoot(asset_server.load(
GltfAssetLabel::Scene(0).from_asset("models/TonemappingTest/TonemappingTest.gltf"),
)),
SceneNumber(1),
@@ -108,7 +108,7 @@ fn setup_basic_scene(mut commands: Commands, asset_server: Res<AssetServer>) {
// Flight Helmet
commands.spawn((
SceneRoot(
WorldAssetRoot(
asset_server
.load(GltfAssetLabel::Scene(0).from_asset("models/FlightHelmet/FlightHelmet.gltf")),
),
+2 -2
View File
@@ -125,7 +125,7 @@ fn setup(
// Spawn the two HLODs.
commands.spawn((
SceneRoot(
WorldAssetRoot(
asset_server
.load(GltfAssetLabel::Scene(0).from_asset("models/FlightHelmet/FlightHelmet.gltf")),
),
@@ -133,7 +133,7 @@ fn setup(
));
commands.spawn((
SceneRoot(
WorldAssetRoot(
asset_server.load(
GltfAssetLabel::Scene(0)
.from_asset("models/FlightHelmetLowPoly/FlightHelmetLowPoly.gltf"),
+1 -1
View File
@@ -60,7 +60,7 @@ fn main() {
/// Initializes the scene.
fn setup(mut commands: Commands, asset_server: Res<AssetServer>, app_settings: Res<AppSettings>) {
// Spawn the glTF scene.
commands.spawn(SceneRoot(asset_server.load(
commands.spawn(WorldAssetRoot(asset_server.load(
GltfAssetLabel::Scene(0).from_asset("models/VolumetricFogExample/VolumetricFogExample.glb"),
)));
+1 -1
View File
@@ -469,7 +469,7 @@ Example | Description
Example | Description
--- | ---
[BSN example](../examples/scene/bsn.rs) | Demonstrates how to use BSN to compose scenes
[Scene](../examples/scene/scene.rs) | Demonstrates loading from and saving scenes to files
[World Serialization](../examples/scene/world_serialization.rs) | Demonstrates loading from and saving world to files
### Shaders
+8 -5
View File
@@ -2,7 +2,9 @@
use std::f32::consts::PI;
use bevy::{light::CascadeShadowConfigBuilder, prelude::*, scene::SceneInstanceReady};
use bevy::{
light::CascadeShadowConfigBuilder, prelude::*, world_serialization::WorldInstanceReady,
};
// An example asset that contains a mesh and animation.
const GLTF_PATH: &str = "models/animated/Fox.glb";
@@ -50,9 +52,10 @@ fn setup_mesh_and_animation(
};
// Start loading the asset as a scene and store a reference to it in a
// SceneRoot component. This component will automatically spawn a scene
// WorldAssetRoot component. This component will automatically spawn a scene
// containing our mesh once it has loaded.
let mesh_scene = SceneRoot(asset_server.load(GltfAssetLabel::Scene(0).from_asset(GLTF_PATH)));
let mesh_scene =
WorldAssetRoot(asset_server.load(GltfAssetLabel::Scene(0).from_asset(GLTF_PATH)));
// Spawn an entity with our components, and connect it to an observer that
// will trigger when the scene is loaded and spawned.
@@ -62,7 +65,7 @@ fn setup_mesh_and_animation(
}
fn play_animation_when_ready(
scene_ready: On<SceneInstanceReady>,
scene_ready: On<WorldInstanceReady>,
mut commands: Commands,
children: Query<&Children>,
animations_to_play: Query<&AnimationToPlay>,
@@ -71,7 +74,7 @@ fn play_animation_when_ready(
// The entity we spawned in `setup_mesh_and_animation` is the trigger's target.
// Start by finding the AnimationToPlay component we added to that entity.
if let Ok(animation_to_play) = animations_to_play.get(scene_ready.entity) {
// The SceneRoot component will have spawned the scene as a hierarchy
// The WorldAssetRoot component will have spawned the scene as a hierarchy
// of entities parented to our entity. Since the asset contained a skinned
// mesh and animations, it will also have spawned an animation player
// component. Search our entity's descendants to find the animation player.
+4 -4
View File
@@ -4,7 +4,7 @@ use std::{f32::consts::PI, time::Duration};
use bevy::{
animation::RepeatAnimation, light::CascadeShadowConfigBuilder, prelude::*,
scene::SceneInstanceReady,
world_serialization::WorldInstanceReady,
};
const FOX_PATH: &str = "models/animated/Fox.glb";
@@ -127,7 +127,7 @@ fn spawn_fox_asset_when_ready(
// Fox
commands
.spawn(SceneRoot(
.spawn(WorldAssetRoot(
fox.default_scene
.clone()
.expect("a default scene exists in this file"),
@@ -137,10 +137,10 @@ fn spawn_fox_asset_when_ready(
// An `AnimationPlayer` is automatically added to the scene when loading the
// glTF file, so it already exists on the appropriate entity when
// `SceneInstanceReady` fires. There will be only one player in this example,
// `WorldInstanceReady` fires. There will be only one player in this example,
// so we use `Single`.
fn setup_scene(
_ready: On<SceneInstanceReady>,
_ready: On<WorldInstanceReady>,
mut commands: Commands,
animations: Res<Animations>,
player: Single<(Entity, &mut AnimationPlayer)>,
+1 -1
View File
@@ -125,7 +125,7 @@ fn setup(
));
// Fox
commands.spawn(SceneRoot(
commands.spawn(WorldAssetRoot(
asset_server.load(GltfAssetLabel::Scene(0).from_asset(FOX_PATH)),
));
+1 -1
View File
@@ -239,7 +239,7 @@ fn setup_scene(
));
commands.spawn((
SceneRoot(
WorldAssetRoot(
asset_server.load(GltfAssetLabel::Scene(0).from_asset("models/animated/Fox.glb")),
),
Transform::from_scale(Vec3::splat(0.07)),
+1 -1
View File
@@ -140,7 +140,7 @@ fn setup_scene(
// Spawn the fox.
commands.spawn((
SceneRoot(
WorldAssetRoot(
asset_server.load(GltfAssetLabel::Scene(0).from_asset("models/animated/Fox.glb")),
),
Transform::from_scale(Vec3::splat(0.07)),
+3 -3
View File
@@ -2,7 +2,7 @@
//!
//! Also illustrates how to read morph target names in `name_morphs`.
use bevy::{prelude::*, scene::SceneInstanceReady};
use bevy::{prelude::*, world_serialization::WorldInstanceReady};
use std::f32::consts::PI;
const GLTF_PATH: &str = "models/animated/MorphStressTest.gltf";
@@ -40,7 +40,7 @@ fn setup(
graph_handle: graphs.add(graph),
index,
},
SceneRoot(asset_server.load(GltfAssetLabel::Scene(0).from_asset(GLTF_PATH))),
WorldAssetRoot(asset_server.load(GltfAssetLabel::Scene(0).from_asset(GLTF_PATH))),
))
.observe(play_animation_when_ready);
@@ -56,7 +56,7 @@ fn setup(
}
fn play_animation_when_ready(
scene_ready: On<SceneInstanceReady>,
scene_ready: On<WorldInstanceReady>,
mut commands: Commands,
children: Query<&Children>,
animations_to_play: Query<&AnimationToPlay>,
+1 -1
View File
@@ -23,7 +23,7 @@ fn setup(mut commands: Commands, asset_server: Res<AssetServer>) {
// You should see the changes immediately show up in your app.
// mesh
commands.spawn(SceneRoot(scene_handle));
commands.spawn(WorldAssetRoot(scene_handle));
// light
commands.spawn((
DirectionalLight::default(),
+1 -1
View File
@@ -241,7 +241,7 @@ fn wait_on_load(
// All gltfs must exist because this is guarded by the `AssetBarrier`.
let gltf = gltfs.get(&foxes.0[index]).unwrap();
let scene = gltf.scenes.first().unwrap().clone();
commands.spawn((SceneRoot(scene), Transform::from_translation(position)));
commands.spawn((WorldAssetRoot(scene), Transform::from_translation(position)));
}
}
}
+1 -1
View File
@@ -77,7 +77,7 @@ fn setup(
commands.spawn((
Name::new("Fox"),
SceneRoot(
WorldAssetRoot(
asset_server.load(GltfAssetLabel::Scene(0).from_asset("models/animated/Fox.glb")),
),
// Note: the scale adjustment is purely an accident of our fox model, which renders
+8 -8
View File
@@ -2,7 +2,7 @@
use bevy::{
audio::AudioPlugin, color::palettes, gltf::GltfMaterialName, prelude::*,
scene::SceneInstanceReady,
world_serialization::WorldInstanceReady,
};
fn main() {
@@ -13,7 +13,7 @@ fn main() {
.run();
}
/// This is added to a [`SceneRoot`] and will cause the [`StandardMaterial::base_color`]
/// This is added to a [`WorldAssetRoot`] and will cause the [`StandardMaterial::base_color`]
/// of materials with [`GltfMaterialName`] equal to `LeatherPartsMat`.
#[derive(Component)]
struct ColorOverride(Color);
@@ -33,27 +33,27 @@ fn setup_scene(mut commands: Commands, asset_server: Res<AssetServer>) {
let flight_helmet = asset_server
.load(GltfAssetLabel::Scene(0).from_asset("models/FlightHelmet/FlightHelmet.gltf"));
// This model will keep its original materials
commands.spawn(SceneRoot(flight_helmet.clone()));
commands.spawn(WorldAssetRoot(flight_helmet.clone()));
// This model will be tinted red
commands.spawn((
SceneRoot(flight_helmet.clone()),
WorldAssetRoot(flight_helmet.clone()),
Transform::from_xyz(-1.25, 0., 0.),
ColorOverride(palettes::tailwind::RED_300.into()),
));
// This model will be tinted green
commands.spawn((
SceneRoot(flight_helmet),
WorldAssetRoot(flight_helmet),
Transform::from_xyz(1.25, 0., 0.),
ColorOverride(palettes::tailwind::GREEN_300.into()),
));
}
/// On [`SceneInstanceReady`], iterates over all descendants of the scene
/// On [`WorldInstanceReady`], iterates over all descendants of the scene
/// and modifies the tint of the material for the materials named `LeatherPartsMat`.
///
/// If the [`SceneRoot`] does not have a [`ColorOverride`], it is skipped.
/// If the [`WorldAssetRoot`] does not have a [`ColorOverride`], it is skipped.
fn change_material(
scene_ready: On<SceneInstanceReady>,
scene_ready: On<WorldInstanceReady>,
mut commands: Commands,
children: Query<&Children>,
color_override: Query<&ColorOverride>,
@@ -10,7 +10,7 @@ use bevy::{
light::CascadeShadowConfigBuilder,
platform::collections::{HashMap, HashSet},
prelude::*,
scene::SceneInstanceReady,
world_serialization::WorldInstanceReady,
};
use chacha20::ChaCha8Rng;
use rand::{RngExt, SeedableRng};
@@ -53,14 +53,14 @@ fn setup_mesh_and_animation(mut commands: Commands, asset_server: Res<AssetServe
// Spawn an entity with our components, and connect it to an observer that
// will trigger when the scene is loaded and spawned.
commands
.spawn(SceneRoot(
.spawn(WorldAssetRoot(
asset_server.load(GltfAssetLabel::Scene(0).from_asset(GLTF_PATH)),
))
.observe(play_animation_when_ready);
}
fn play_animation_when_ready(
scene_ready: On<SceneInstanceReady>,
scene_ready: On<WorldInstanceReady>,
mut commands: Commands,
children: Query<&Children>,
mut players: Query<(&mut AnimationPlayer, &AnimationToPlay)>,
+1 -1
View File
@@ -55,7 +55,7 @@ fn main() {
fn setup(mut commands: Commands, asset_server: Res<AssetServer>) {
commands.spawn((
SceneRoot(
WorldAssetRoot(
asset_server
.load(GltfAssetLabel::Scene(0).from_asset("models/barycentric/barycentric.gltf")),
),
+1 -1
View File
@@ -25,7 +25,7 @@ fn setup(mut commands: Commands, asset_server: Res<AssetServer>) {
));
// Spawn the first scene in `models/SimpleSkin/SimpleSkin.gltf`
commands.spawn(SceneRoot(asset_server.load(
commands.spawn(WorldAssetRoot(asset_server.load(
GltfAssetLabel::Scene(0).from_asset("models/SimpleSkin/SimpleSkin.gltf"),
)));
}
+1 -1
View File
@@ -43,7 +43,7 @@ fn setup(mut commands: Commands, asset_server: Res<AssetServer>) {
}
.build(),
));
commands.spawn(SceneRoot(asset_server.load(
commands.spawn(WorldAssetRoot(asset_server.load(
GltfAssetLabel::Scene(0).from_asset("models/FlightHelmet/FlightHelmet.gltf"),
)));
}
+1 -1
View File
@@ -28,7 +28,7 @@ fn setup(mut commands: Commands, asset_server: Res<AssetServer>) {
});
// a barebones scene containing one of each gltf_extra type
commands.spawn(SceneRoot(asset_server.load(
commands.spawn(WorldAssetRoot(asset_server.load(
GltfAssetLabel::Scene(0).from_asset("models/extras/gltf_extras.glb"),
)));
+1 -1
View File
@@ -62,7 +62,7 @@ fn setup(mut commands: Commands, asset_server: Res<AssetServer>) {
DirectionalLight::default(),
));
commands.spawn(SceneRoot(asset_server.load(
commands.spawn(WorldAssetRoot(asset_server.load(
GltfAssetLabel::Scene(0).from_asset("models/GltfPrimitives/gltf_primitives.glb"),
)));
}
+2 -2
View File
@@ -37,7 +37,7 @@ fn setup(mut commands: Commands, asset_server: Res<AssetServer>) {
// Spawn the scene as a child of this entity at the given transform
commands.spawn((
Transform::from_xyz(-1.0, 0.0, 0.0),
SceneRoot(
WorldAssetRoot(
asset_server
.load(GltfAssetLabel::Scene(0).from_asset("models/FlightHelmet/FlightHelmet.gltf")),
),
@@ -45,7 +45,7 @@ fn setup(mut commands: Commands, asset_server: Res<AssetServer>) {
// Spawn a second scene, and add a tag component to be able to target it later
commands.spawn((
SceneRoot(
WorldAssetRoot(
asset_server
.load(GltfAssetLabel::Scene(0).from_asset("models/FlightHelmet/FlightHelmet.gltf")),
),
+12 -12
View File
@@ -12,9 +12,9 @@ pub fn strip_base_url(path: String) -> String {
#[derive(Resource)]
pub struct CityAssets {
pub untyped_assets: Vec<UntypedHandle>,
pub cars: Vec<Handle<Scene>>,
pub crossroad: Handle<Scene>,
pub road_straight: Handle<Scene>,
pub cars: Vec<Handle<WorldAsset>>,
pub crossroad: Handle<WorldAsset>,
pub road_straight: Handle<WorldAsset>,
pub high_density: Buildings,
pub medium_density: Buildings,
pub low_density: Buildings,
@@ -23,14 +23,14 @@ pub struct CityAssets {
Handle<StandardMaterial>,
Handle<StandardMaterial>,
),
pub tree_small: Handle<Scene>,
pub tree_large: Handle<Scene>,
pub path_stones_long: Handle<Scene>,
pub fence: Handle<Scene>,
pub tree_small: Handle<WorldAsset>,
pub tree_large: Handle<WorldAsset>,
pub path_stones_long: Handle<WorldAsset>,
pub fence: Handle<WorldAsset>,
}
impl CityAssets {
pub fn get_random_car<R: RngExt>(&self, rng: &mut R) -> Handle<Scene> {
pub fn get_random_car<R: RngExt>(&self, rng: &mut R) -> Handle<WorldAsset> {
self.cars[rng.random_range(0..self.cars.len())].clone()
}
}
@@ -205,17 +205,17 @@ pub fn load_assets(
(mesh, default_material, grass_material)
};
let tree_small: Handle<Scene> =
let tree_small: Handle<WorldAsset> =
load_asset!(GltfAssetLabel::Scene(0)
.from_asset(format!("{base_url}/city-kit-suburban/tree-small.glb")));
let tree_large: Handle<Scene> =
let tree_large: Handle<WorldAsset> =
load_asset!(GltfAssetLabel::Scene(0)
.from_asset(format!("{base_url}/city-kit-suburban/tree-large.glb")));
let path_stones_long: Handle<Scene> = load_asset!(GltfAssetLabel::Scene(0)
let path_stones_long: Handle<WorldAsset> = load_asset!(GltfAssetLabel::Scene(0)
.from_asset(format!("{base_url}/city-kit-suburban/path-stones-long.glb")));
let fence: Handle<Scene> = load_asset!(
let fence: Handle<WorldAsset> = load_asset!(
GltfAssetLabel::Scene(0).from_asset(format!("{base_url}/city-kit-suburban/fence.glb"))
);
@@ -88,7 +88,7 @@ fn spawn_roads_and_cars<R: RngExt>(
let z = offset.z;
commands.spawn((
SceneRoot(assets.crossroad.clone()),
WorldAssetRoot(assets.crossroad.clone()),
Transform::from_xyz(x, 0.0, z),
));
@@ -112,7 +112,7 @@ fn spawn_roads_and_cars<R: RngExt>(
))
.with_children(|commands| {
commands.spawn((
SceneRoot(assets.road_straight.clone()),
WorldAssetRoot(assets.road_straight.clone()),
Transform::from_translation(Vec3::new(2.75, 0.0, 0.0))
.with_scale(Vec3::new(4.5, 1.0, 1.0)),
));
@@ -122,7 +122,7 @@ fn spawn_roads_and_cars<R: RngExt>(
if rng.random::<f32>() < max_car_density {
commands.spawn((
SceneRoot(assets.get_random_car(rng)),
WorldAssetRoot(assets.get_random_car(rng)),
Transform::from_translation(car_pos + Vec3::new(0.0, 0.0, -0.15))
.with_scale(Vec3::splat(0.15))
.with_rotation(Quat::from_axis_angle(
@@ -139,7 +139,7 @@ fn spawn_roads_and_cars<R: RngExt>(
if rng.random::<f32>() < max_car_density {
commands.spawn((
SceneRoot(assets.get_random_car(rng)),
WorldAssetRoot(assets.get_random_car(rng)),
Transform::from_translation(car_pos + Vec3::new(0.0, 0.0, 0.15))
.with_scale(Vec3::splat(0.15))
.with_rotation(Quat::from_axis_angle(
@@ -169,7 +169,7 @@ fn spawn_roads_and_cars<R: RngExt>(
))
.with_children(|commands| {
commands.spawn((
SceneRoot(assets.road_straight.clone()),
WorldAssetRoot(assets.road_straight.clone()),
Transform::from_translation(Vec3::new(0.0, 0.0, 2.0))
.with_scale(Vec3::new(3.0, 1.0, 1.0))
.with_rotation(Quat::from_axis_angle(Vec3::Y, std::f32::consts::FRAC_PI_2)),
@@ -180,7 +180,7 @@ fn spawn_roads_and_cars<R: RngExt>(
if rng.random::<f32>() < max_car_density {
commands.spawn((
SceneRoot(assets.get_random_car(rng)),
WorldAssetRoot(assets.get_random_car(rng)),
Transform::from_translation(car_pos + Vec3::new(0.15, 0.0, 0.0))
.with_scale(Vec3::splat(0.15)),
Car {
@@ -193,7 +193,7 @@ fn spawn_roads_and_cars<R: RngExt>(
if rng.random::<f32>() < max_car_density {
commands.spawn((
SceneRoot(assets.get_random_car(rng)),
WorldAssetRoot(assets.get_random_car(rng)),
Transform::from_translation(car_pos + Vec3::new(-0.15, 0.0, 0.0))
.with_scale(Vec3::splat(0.15))
.with_rotation(Quat::from_axis_angle(Vec3::Y, std::f32::consts::PI)),
@@ -228,18 +228,18 @@ fn spawn_low_density<R: RngExt>(
}
for i in 0..=6 {
commands.spawn((
SceneRoot(assets.fence.clone()),
WorldAssetRoot(assets.fence.clone()),
Transform::from_translation(Vec3::new(2.75, 0.0, 0.75 + i as f32 * 0.4) + offset)
.with_rotation(Quat::from_axis_angle(Vec3::Y, std::f32::consts::FRAC_PI_2)),
));
}
for z in 0..=8 {
commands.spawn((
SceneRoot(assets.tree_small.clone()),
WorldAssetRoot(assets.tree_small.clone()),
Transform::from_translation(Vec3::new(0.75, 0.0, 0.75 + z as f32 * 0.3) + offset),
));
commands.spawn((
SceneRoot(assets.tree_small.clone()),
WorldAssetRoot(assets.tree_small.clone()),
Transform::from_translation(Vec3::new(4.75, 0.0, 0.75 + z as f32 * 0.3) + offset),
));
}
@@ -264,13 +264,13 @@ fn spawn_medium_density<R: RngExt>(
break;
}
commands.spawn((
SceneRoot(assets.tree_large.clone()),
WorldAssetRoot(assets.tree_large.clone()),
Transform::from_translation(
Vec3::new(tree_x + x as f32 * x_factor, 0.0, 1.75) + offset,
),
));
commands.spawn((
SceneRoot(assets.tree_large.clone()),
WorldAssetRoot(assets.tree_large.clone()),
Transform::from_translation(
Vec3::new(tree_x + x as f32 * x_factor, 0.0, 2.25) + offset,
),
@@ -286,17 +286,17 @@ fn spawn_medium_density<R: RngExt>(
for x in 0..=10 {
commands.spawn((
SceneRoot(assets.path_stones_long.clone()),
WorldAssetRoot(assets.path_stones_long.clone()),
Transform::from_translation(Vec3::new(0.75 + (x as f32 * 0.4), 0.02, 2.0) + offset)
.with_scale(Vec3::new(1.0, 2.0, 1.0))
.with_rotation(Quat::from_axis_angle(Vec3::Y, std::f32::consts::FRAC_PI_2)),
));
commands.spawn((
SceneRoot(assets.fence.clone()),
WorldAssetRoot(assets.fence.clone()),
Transform::from_translation(Vec3::new(0.75 + (x as f32 * 0.4), 0.02, 1.85) + offset),
));
commands.spawn((
SceneRoot(assets.fence.clone()),
WorldAssetRoot(assets.fence.clone()),
Transform::from_translation(Vec3::new(0.75 + (x as f32 * 0.4), 0.02, 2.15) + offset),
));
}
@@ -339,10 +339,10 @@ fn spawn_forest<R: RngExt>(
match rng.random_range(0..3) {
0 => {}
1 => {
commands.spawn((SceneRoot(assets.tree_small.clone()), transform));
commands.spawn((WorldAssetRoot(assets.tree_small.clone()), transform));
}
2 => {
commands.spawn((SceneRoot(assets.tree_large.clone()), transform));
commands.spawn((WorldAssetRoot(assets.tree_large.clone()), transform));
}
_ => {}
}
+2 -2
View File
@@ -15,9 +15,9 @@ use bevy::{
},
post_process::bloom::Bloom,
prelude::*,
scene::SceneInstanceReady,
window::{PresentMode, WindowResolution},
winit::WinitSettings,
world_serialization::WorldInstanceReady,
};
use crate::{assets::strip_base_url, settings::Settings};
@@ -278,7 +278,7 @@ fn add_no_cpu_culling(
}
fn add_no_cpu_culling_on_scene_ready(
scene_ready: On<SceneInstanceReady>,
scene_ready: On<WorldInstanceReady>,
mut commands: Commands,
children: Query<&Children>,
meshes: Query<(), (With<Mesh3d>, Without<NoCpuCulling>)>,
+7 -7
View File
@@ -26,7 +26,7 @@ use bevy::{
batching::NoAutomaticBatching, occlusion_culling::OcclusionCulling, render_resource::Face,
view::NoIndirectDrawing,
},
scene::SceneInstanceReady,
world_serialization::WorldInstanceReady,
};
use bevy::{
camera::Hdr,
@@ -184,12 +184,12 @@ pub fn setup(mut commands: Commands, asset_server: Res<AssetServer>, args: Res<A
let bistro_exterior = asset_server.load("bistro_exterior/BistroExterior.gltf#Scene0");
commands
.spawn((SceneRoot(bistro_exterior.clone()), Spin))
.spawn((WorldAssetRoot(bistro_exterior.clone()), Spin))
.observe(proc_scene);
let bistro_interior = asset_server.load("bistro_interior_wine/BistroInterior_Wine.gltf#Scene0");
commands
.spawn((SceneRoot(bistro_interior.clone()), Spin))
.spawn((WorldAssetRoot(bistro_interior.clone()), Spin))
.observe(proc_scene);
let mut count = 0;
@@ -208,14 +208,14 @@ pub fn setup(mut commands: Commands, asset_server: Res<AssetServer>, args: Res<A
}
commands
.spawn((
SceneRoot(bistro_exterior.clone()),
WorldAssetRoot(bistro_exterior.clone()),
Transform::from_xyz(x as f32 * 150.0, 0.0, z as f32 * 150.0),
Spin,
))
.observe(proc_scene);
commands
.spawn((
SceneRoot(bistro_interior.clone()),
WorldAssetRoot(bistro_interior.clone()),
Transform::from_xyz(x as f32 * 150.0, 0.3, z as f32 * 150.0 - 0.2),
Spin,
))
@@ -228,7 +228,7 @@ pub fn setup(mut commands: Commands, asset_server: Res<AssetServer>, args: Res<A
if !args.no_gltf_lights {
// In Repo glTF
commands.spawn((
SceneRoot(asset_server.load("BistroExteriorFakeGI.gltf#Scene0")),
WorldAssetRoot(asset_server.load("BistroExteriorFakeGI.gltf#Scene0")),
Spin,
));
}
@@ -337,7 +337,7 @@ pub fn all_children<F: FnMut(Entity)>(
#[allow(clippy::type_complexity, clippy::too_many_arguments)]
pub fn proc_scene(
scene_ready: On<SceneInstanceReady>,
scene_ready: On<WorldInstanceReady>,
mut commands: Commands,
children: Query<&Children>,
has_std_mat: Query<&MeshMaterial3d<StandardMaterial>>,
@@ -28,9 +28,9 @@ use bevy::{
},
view::NoIndirectDrawing,
},
scene::SceneInstanceReady,
window::{PresentMode, WindowResolution},
winit::WinitSettings,
world_serialization::WorldInstanceReady,
};
#[derive(FromArgs, Resource, Clone)]
@@ -145,7 +145,7 @@ pub fn setup(
let hotel_01 = asset_server.load("hotel_01.glb#Scene0");
commands
.spawn((
SceneRoot(hotel_01.clone()),
WorldAssetRoot(hotel_01.clone()),
Transform::from_scale(Vec3::splat(0.01)),
PostProcScene,
Spin,
@@ -165,7 +165,7 @@ pub fn setup(
continue;
}
commands.spawn((
SceneRoot(hotel_01.clone()),
WorldAssetRoot(hotel_01.clone()),
Transform::from_xyz(x as f32 * 50.0, 0.0, z as f32 * 50.0)
.with_scale(Vec3::splat(0.01)),
Spin,
@@ -263,7 +263,7 @@ pub fn setup(
// Each unique so instances are maintained.
#[allow(clippy::too_many_arguments)]
pub fn assign_rng_materials(
scene_ready: On<SceneInstanceReady>,
scene_ready: On<WorldInstanceReady>,
mut commands: Commands,
mut materials: ResMut<Assets<StandardMaterial>>,
mut images: ResMut<Assets<Image>>,
@@ -271,7 +271,7 @@ pub fn assign_rng_materials(
mesh_instances: Query<(Entity, &Mesh3d)>,
args: Res<Args>,
asset_server: Res<AssetServer>,
scenes: Query<&SceneRoot>,
scenes: Query<&WorldAssetRoot>,
) {
if !args.random_materials {
return;
@@ -11,7 +11,7 @@ license = "MIT OR Apache-2.0"
anyhow = "1"
bevy = { path = "../../../", default-features = false, features = [
"bevy_asset",
"bevy_ecs_serialization",
"bevy_world_serialization",
"bevy_pbr",
"ktx2",
"jpeg",
@@ -67,7 +67,7 @@ fn setup(mut commands: Commands, asset_server: Res<AssetServer>) {
Transform::from_xyz(-1.0, 2.0, -3.0),
));
commands.spawn(SceneRoot(
commands.spawn(WorldAssetRoot(
asset_server.load(
// This seems to be the correct path but bevy doesn't resolve it.
GltfAssetLabel::Scene(0)
+1 -1
View File
@@ -25,7 +25,7 @@ bevy = { path = "../../", default-features = false, features = [
"bevy_gltf",
"bevy_pbr",
"bevy_render",
"bevy_ecs_serialization",
"bevy_world_serialization",
"bevy_sprite",
"bevy_state",
"bevy_text",
@@ -1,21 +1,21 @@
//! This example demonstrates how to load scene data from files and then dynamically
//! This example demonstrates how to load world data from files and then dynamically
//! apply that data to entities in your Bevy `World`. This includes spawning new
//! entities and applying updates to existing ones. Scenes in Bevy encapsulate
//! serialized and deserialized `Components` or `Resources` so that you can easily
//! entities and applying updates to existing ones. World serialization in Bevy
//! serializes and deserializes `Components` and `Resources` so that you can easily
//! store, load, and manipulate data outside of a purely code-driven context.
//!
//! This example also shows how to do the following:
//! * Register your custom types for reflection, which allows them to be serialized,
//! deserialized, and manipulated dynamically.
//! * Skip serialization of fields you don't want stored in your scene files (like
//! * Skip serialization of fields you don't want stored in your world files (like
//! runtime values that should always be computed dynamically).
//! * Save a new scene to disk to show how it can be updated compared to the original
//! scene file (and how that updated scene file might then be used later on).
//! * Save a new world to disk to show how it can be updated compared to the original
//! world file (and how that updated world file might then be used later on).
//!
//! The example proceeds by creating components and resources, registering their types,
//! loading a scene from a file, logging when changes are detected, and finally saving
//! a new scene file to disk. This is useful for anyone wanting to see how to integrate
//! file-based scene workflows into their Bevy projects.
//! loading a world from a file, logging when changes are detected, and finally saving
//! a new world file to disk. This is useful for anyone wanting to see how to integrate
//! file-based ECS workflows into their Bevy projects.
//!
//! # Note on working with files
//!
@@ -38,7 +38,7 @@ fn main() {
.add_plugins(DefaultPlugins)
.add_systems(
Startup,
(save_scene_system, load_scene_system, infotext_system),
(save_world_system, load_world_system, infotext_system),
)
.add_systems(Update, (log_system, panic_on_fail))
.run();
@@ -56,7 +56,7 @@ fn main() {
/// A sample component that is fully serializable.
///
/// This component has public `x` and `y` fields that will be included in
/// the scene files. Notice how it derives `Default`, `Reflect`, and declares
/// the world files. Notice how it derives `Default`, `Reflect`, and declares
/// itself as a reflected component with `#[reflect(Component)]`.
#[derive(Component, Reflect, Default)]
#[reflect(Component)] // this tells the reflect derive to also reflect component behaviors
@@ -70,13 +70,13 @@ struct ComponentA {
/// A sample component that includes both serializable and non-serializable fields.
///
/// This is useful for skipping serialization of runtime data or fields you
/// don't want written to scene files.
/// don't want written to world files.
#[derive(Component, Reflect)]
#[reflect(Component)]
struct ComponentB {
/// A string field that will be serialized.
pub value: String,
/// A `Duration` field that should never be serialized to the scene file, so we skip it.
/// A `Duration` field that should never be serialized to the world file, so we skip it.
#[reflect(skip_serializing)]
pub _time_since_startup: Duration,
}
@@ -94,7 +94,7 @@ impl FromWorld for ComponentB {
}
}
/// A simple resource that also derives `Reflect`, allowing it to be stored in scenes.
/// A simple resource that also derives `Reflect`, allowing it to be stored in world files.
///
/// Just like a component, you can skip serializing fields or implement `FromWorld` if needed.
#[derive(Resource, Reflect, Default)]
@@ -104,26 +104,26 @@ struct ResourceA {
pub score: u32,
}
/// # Scene File Paths
/// # World File Paths
///
/// `SCENE_FILE_PATH` points to the original scene file that we'll be loading.
/// `NEW_SCENE_FILE_PATH` points to the new scene file that we'll be creating
/// `WORLD_FILE_PATH` points to the original world file that we'll be loading.
/// `NEW_WORLD_FILE_PATH` points to the new world file that we'll be creating
/// (and demonstrating how to serialize to disk).
///
/// The initial scene file will be loaded below and not change when the scene is saved.
const SCENE_FILE_PATH: &str = "scenes/load_scene_example.scn.ron";
/// The initial world file will be loaded below and not change when the world is saved.
const WORLD_FILE_PATH: &str = "serialized_worlds/load_scene_example.scn.ron";
/// The new, updated scene data will be saved here so that you can see the changes.
const NEW_SCENE_FILE_PATH: &str = "scenes/load_scene_example-new.scn.ron";
/// The new, updated world data will be saved here so that you can see the changes.
const NEW_WORLD_FILE_PATH: &str = "serialized_worlds/load_scene_example-new.scn.ron";
/// Loads a scene from an asset file and spawns it in the current world.
/// Loads a world from an asset file and spawns it in the current world.
///
/// Spawning a `DynamicSceneRoot` creates a new parent entity, which then spawns new
/// instances of the scene's entities as its children. If you modify the
/// `SCENE_FILE_PATH` scene file, or if you enable file watching, you can see
/// Spawning a `DynamicWorldRoot` creates a new parent entity, which then spawns new
/// instances of the world's entities as its children. If you modify the
/// `WORLD_FILE_PATH` file, or if you enable file watching, you can see
/// changes reflected immediately.
fn load_scene_system(mut commands: Commands, asset_server: Res<AssetServer>) {
commands.spawn(DynamicSceneRoot(asset_server.load(SCENE_FILE_PATH)));
fn load_world_system(mut commands: Commands, asset_server: Res<AssetServer>) {
commands.spawn(DynamicWorldRoot(asset_server.load(WORLD_FILE_PATH)));
commands.spawn((
Camera3d::default(),
Transform::from_xyz(1.0, 1.0, 1.0).looking_at(Vec3::new(0.0, 0.25, 0.0), Vec3::Y),
@@ -138,7 +138,7 @@ fn load_scene_system(mut commands: Commands, asset_server: Res<AssetServer>) {
/// has been recently added.
///
/// Any time a `ComponentA` is modified, that change will appear here. This system
/// demonstrates how you might detect and handle scene updates at runtime.
/// demonstrates how you might detect and handle world updates at runtime.
fn log_system(
query: Query<(Entity, &ComponentA), Changed<ComponentA>>,
res: Option<Res<ResourceA>>,
@@ -157,21 +157,20 @@ fn log_system(
}
}
/// Demonstrates how to create a new scene from scratch, populate it with data,
/// and then serialize it to a file. The new file is written to `NEW_SCENE_FILE_PATH`.
/// Demonstrates how to create a new world from scratch, populate it with data,
/// and then serialize it to a file. The new file is written to `NEW_WORLD_FILE_PATH`.
///
/// This system creates a fresh world, duplicates the type registry so that our
/// custom component types are recognized, spawns some sample entities and resources,
/// and then serializes the resulting dynamic scene.
fn save_scene_system(world: &mut World) {
/// and then serializes the resulting dynamic world.
fn save_world_system(world: &mut World) {
let asset_server = world.resource::<AssetServer>().clone();
// The `TypeRegistry` resource contains information about all registered types (including components).
// This is used to construct scenes, so we'll want to ensure that we use the registry from the
// This is used to construct worlds, so we'll want to ensure that we use the registry from the
// main world. To do this, we can simply clone the `AppTypeRegistry` resource.
let type_registry = world.resource::<AppTypeRegistry>().clone();
// Scenes can be created from any ECS World.
// You can either create a new one for the scene or use the current World.
// Any ECS World can be serialized.
// For demonstration purposes, we'll create a new one.
let mut scene_world = World::new();
@@ -182,40 +181,41 @@ fn save_scene_system(world: &mut World) {
ComponentA { x: 1.0, y: 2.0 },
Transform::IDENTITY,
Name::new("joe"),
SceneRoot(asset_server.load("models/FlightHelmet/FlightHelmet.gltf#Scene0")),
WorldAssetRoot(asset_server.load("models/FlightHelmet/FlightHelmet.gltf#Scene0")),
));
scene_world.spawn(ComponentA { x: 3.0, y: 4.0 });
scene_world.insert_resource(ResourceA { score: 1 });
// With our sample world ready to go, we can now create our scene using DynamicScene or DynamicSceneBuilder.
// For simplicity, we will create our scene using DynamicScene:
let scene = DynamicScene::from_world_with(&scene_world, &type_registry.read());
// With our sample world ready to go, we can now create a DynamicWorld from it.
// For simplicity, we will create our scene using DynamicWorld directly, but if
// you need more control, you can use DynamicWorldBuilder.
let dynamic_world = DynamicWorld::from_world_with(&scene_world, &type_registry.read());
// Scenes can be serialized like this:
// Dynamic Worlds can be serialized like this:
let type_registry = world.resource::<AppTypeRegistry>();
let type_registry = type_registry.read();
let serialized_scene = scene.serialize(&type_registry).unwrap();
let serialized_world = dynamic_world.serialize(&type_registry).unwrap();
// Showing the scene in the console
info!("{}", serialized_scene);
// Shows the serialized world in the console
info!("{}", serialized_world);
// Writing the scene to a new file. Using a task to avoid calling the filesystem APIs in a system
// Writing the world to a new file. Using a task to avoid calling the filesystem APIs in a system
// as they are blocking.
//
// This can't work in Wasm as there is no filesystem access.
#[cfg(not(target_arch = "wasm32"))]
IoTaskPool::get()
.spawn(async move {
// Write the scene RON data to file
File::create(format!("assets/{NEW_SCENE_FILE_PATH}"))
.and_then(|mut file| file.write(serialized_scene.as_bytes()))
.expect("Error while writing scene to file");
// Write the world RON data to file
File::create(format!("assets/{NEW_WORLD_FILE_PATH}"))
.and_then(|mut file| file.write(serialized_world.as_bytes()))
.expect("Error while writing world to file");
})
.detach();
}
/// Spawns a simple 2D camera and some text indicating that the user should
/// check the console output for scene loading/saving messages.
/// check the console output for world loading/saving messages.
///
/// This system is only necessary for the info message in the UI.
fn infotext_system(mut commands: Commands) {
@@ -233,11 +233,11 @@ fn infotext_system(mut commands: Commands) {
}
/// To help with Bevy's automated testing, we want the example to close with an appropriate if the
/// scene fails to load. This is most likely not something you want in your own app.
fn panic_on_fail(scenes: Query<&DynamicSceneRoot>, asset_server: Res<AssetServer>) {
for scene in &scenes {
if let Some(LoadState::Failed(err)) = asset_server.get_load_state(&scene.0) {
panic!("Failed to load scene. {err}");
/// world asset fails to load. This is most likely not something you want in your own app.
fn panic_on_fail(world_roots: Query<&DynamicWorldRoot>, asset_server: Res<AssetServer>) {
for world_root in &world_roots {
if let Some(LoadState::Failed(err)) = asset_server.get_load_state(&world_root.0) {
panic!("Failed to load world. {err}");
}
}
}
+4 -4
View File
@@ -64,7 +64,7 @@ struct Bonus {
entity: Option<Entity>,
i: usize,
j: usize,
handle: Handle<Scene>,
handle: Handle<WorldAsset>,
}
#[derive(Resource, Default)]
@@ -142,7 +142,7 @@ fn setup(mut commands: Commands, asset_server: Res<AssetServer>, mut game: ResMu
commands.spawn((
DespawnOnExit(GameState::Playing),
Transform::from_xyz(i as f32, height - 0.2, j as f32),
SceneRoot(cell_scene.clone()),
WorldAssetRoot(cell_scene.clone()),
));
Cell { height }
})
@@ -164,7 +164,7 @@ fn setup(mut commands: Commands, asset_server: Res<AssetServer>, mut game: ResMu
rotation: Quat::from_rotation_y(-PI / 2.),
..default()
},
SceneRoot(
WorldAssetRoot(
asset_server
.load(GltfAssetLabel::Scene(0).from_asset("models/AlienCake/alien.glb")),
),
@@ -347,7 +347,7 @@ fn spawn_bonus(
game.board[game.bonus.j][game.bonus.i].height + 0.2,
game.bonus.j as f32,
),
SceneRoot(game.bonus.handle.clone()),
WorldAssetRoot(game.bonus.handle.clone()),
children![(
PointLight {
color: Color::srgb(1.0, 1.0, 0.0),
+2 -2
View File
@@ -146,7 +146,7 @@ fn load_level_1(
loading_data.loading_assets.push(fox.clone().into());
// Spawn the fox.
commands.spawn((
SceneRoot(fox.clone()),
WorldAssetRoot(fox.clone()),
Transform::from_xyz(0.0, 0.0, 0.0),
LevelComponents,
));
@@ -180,7 +180,7 @@ fn load_level_2(
loading_data
.loading_assets
.push(helmet_scene.clone().into());
commands.spawn((SceneRoot(helmet_scene.clone()), LevelComponents));
commands.spawn((WorldAssetRoot(helmet_scene.clone()), LevelComponents));
// Spawn the light.
commands.spawn((
+3 -3
View File
@@ -9,9 +9,9 @@ use bevy::{
light::CascadeShadowConfigBuilder,
post_process::motion_blur::MotionBlur,
prelude::*,
scene::SceneInstanceReady,
window::{PresentMode, WindowResolution},
winit::WinitSettings,
world_serialization::WorldInstanceReady,
};
#[derive(FromArgs, Resource)]
@@ -179,7 +179,7 @@ fn setup(
commands.entity(ring_parent).with_children(|builder| {
builder
.spawn((
SceneRoot(fox_handle.clone()),
WorldAssetRoot(fox_handle.clone()),
Transform::from_xyz(x, 0.0, z)
.with_scale(Vec3::splat(0.01))
.with_rotation(base_rotation * Quat::from_rotation_y(-fox_angle)),
@@ -249,7 +249,7 @@ fn setup(
// Once the scene is loaded, start the animation
fn setup_scene_once_loaded(
scene_ready: On<SceneInstanceReady>,
scene_ready: On<WorldInstanceReady>,
animations: Res<Animations>,
foxes: Res<Foxes>,
mut commands: Commands,
+5 -5
View File
@@ -5,9 +5,9 @@ use bevy::{
diagnostic::{FrameTimeDiagnosticsPlugin, LogDiagnosticsPlugin},
post_process::motion_blur::MotionBlur,
prelude::*,
scene::SceneInstanceReady,
window::{PresentMode, WindowResolution},
winit::WinitSettings,
world_serialization::WorldInstanceReady,
};
use chacha20::ChaCha8Rng;
use core::{f32::consts::PI, str::FromStr};
@@ -168,7 +168,7 @@ fn main() {
#[derive(Resource, Default)]
struct MorphAssets {
scene: Handle<Scene>,
scene: Handle<WorldAsset>,
animations: Vec<(Handle<AnimationGraph>, AnimationNodeIndex)>,
}
@@ -353,7 +353,7 @@ fn update(
.spawn((
animation,
Transform::from_xyz(x, y, 0.0),
SceneRoot(assets.scene.clone()),
WorldAssetRoot(assets.scene.clone()),
))
.observe(play_animation)
.observe(set_weights)
@@ -364,7 +364,7 @@ fn update(
}
fn play_animation(
trigger: On<SceneInstanceReady>,
trigger: On<WorldInstanceReady>,
mut commands: Commands,
args: Res<Args>,
children: Query<&Children>,
@@ -390,7 +390,7 @@ fn play_animation(
}
fn set_weights(
trigger: On<SceneInstanceReady>,
trigger: On<WorldInstanceReady>,
args: Res<Args>,
children: Query<&Children>,
mut weight_components: Query<&mut MorphWeights>,
+7 -7
View File
@@ -269,7 +269,7 @@ mod gltf {
DespawnOnExit(CURRENT_SCENE),
));
commands.spawn((
SceneRoot(asset_server.load(
WorldAssetRoot(asset_server.load(
GltfAssetLabel::Scene(0).from_asset("models/FlightHelmet/FlightHelmet.gltf"),
)),
DespawnOnExit(CURRENT_SCENE),
@@ -280,7 +280,7 @@ mod gltf {
mod animation {
use std::{f32::consts::PI, time::Duration};
use bevy::{prelude::*, scene::SceneInstanceReady};
use bevy::{prelude::*, world_serialization::WorldInstanceReady};
const CURRENT_SCENE: super::Scene = super::Scene::Animation;
const FOX_PATH: &str = "models/animated/Fox.glb";
@@ -323,14 +323,14 @@ mod animation {
commands
.spawn((
SceneRoot(asset_server.load(GltfAssetLabel::Scene(0).from_asset(FOX_PATH))),
WorldAssetRoot(asset_server.load(GltfAssetLabel::Scene(0).from_asset(FOX_PATH))),
DespawnOnExit(CURRENT_SCENE),
))
.observe(pause_animation_frame);
}
fn pause_animation_frame(
scene_ready: On<SceneInstanceReady>,
scene_ready: On<WorldInstanceReady>,
children: Query<&Children>,
mut commands: Commands,
animation: Res<Animation>,
@@ -409,7 +409,7 @@ mod gltf_coordinate_conversion {
color::palettes::basic::*,
gltf::{convert_coordinates::GltfConvertCoordinates, GltfLoaderSettings},
prelude::*,
scene::SceneInstanceReady,
world_serialization::WorldInstanceReady,
};
const CURRENT_SCENE: super::Scene = super::Scene::GltfCoordinateConversion;
@@ -450,7 +450,7 @@ mod gltf_coordinate_conversion {
commands
.spawn((
SceneRoot(asset_server.load_with_settings(
WorldAssetRoot(asset_server.load_with_settings(
GltfAssetLabel::Scene(0).from_asset("models/Faces/faces.glb"),
|s: &mut GltfLoaderSettings| {
s.convert_coordinates = Some(GltfConvertCoordinates {
@@ -465,7 +465,7 @@ mod gltf_coordinate_conversion {
}
pub fn show_aabbs(
scene_ready: On<SceneInstanceReady>,
scene_ready: On<WorldInstanceReady>,
mut commands: Commands,
children: Query<&Children>,
meshes: Query<(), With<Mesh3d>>,
@@ -6,7 +6,7 @@
use bevy::{
camera_controller::free_camera::FreeCamera,
gizmos::skinned_mesh_bounds::SkinnedMeshBoundsGizmoConfigGroup, gltf::Gltf,
input::common_conditions::input_just_pressed, prelude::*, scene::InstanceId,
input::common_conditions::input_just_pressed, prelude::*, world_serialization::InstanceId,
};
use std::{f32::consts::*, fmt};
@@ -102,10 +102,10 @@ fn toggle_skinned_mesh_bounds(mut config: ResMut<GizmoConfigStore>) {
fn scene_load_check(
asset_server: Res<AssetServer>,
mut scenes: ResMut<Assets<Scene>>,
mut scenes: ResMut<Assets<WorldAsset>>,
gltf_assets: Res<Assets<Gltf>>,
mut scene_handle: ResMut<SceneHandle>,
mut scene_spawner: ResMut<SceneSpawner>,
mut scene_spawner: ResMut<WorldInstanceSpawner>,
) {
match scene_handle.instance_id {
None => {
+1 -1
View File
@@ -81,7 +81,7 @@ fn setup(
// Finally, our ship that is going to rotate
commands.spawn((
SceneRoot(
WorldAssetRoot(
asset_server
.load(GltfAssetLabel::Scene(0).from_asset("models/ship/craft_speederD.gltf")),
),
+1 -1
View File
@@ -12,7 +12,7 @@ fn main() {
fn setup_scene(mut commands: Commands, asset_server: Res<AssetServer>) {
// add entities to the world
commands.spawn(SceneRoot(
commands.spawn(WorldAssetRoot(
asset_server.load(GltfAssetLabel::Scene(0).from_asset("models/torus/torus.gltf")),
));
// light
+3 -3
View File
@@ -8,7 +8,7 @@ use bevy::{
PrimitiveTopology, VertexAttributeValues,
},
prelude::*,
scene::SceneInstanceReady,
world_serialization::WorldInstanceReady,
};
use std::f32::consts::{FRAC_PI_2, FRAC_PI_4};
@@ -86,7 +86,7 @@ fn spawn_scene(
.entity(entity)
.remove::<PendingScene>()
.insert((
SceneRoot(scene_handle.clone()),
WorldAssetRoot(scene_handle.clone()),
PendingAnimation((graphs.add(graph), graph_node_index)),
))
.observe(play_animation);
@@ -95,7 +95,7 @@ fn spawn_scene(
}
fn play_animation(
trigger: On<SceneInstanceReady>,
trigger: On<WorldInstanceReady>,
mut commands: Commands,
children: Query<&Children>,
animations: Query<&PendingAnimation>,