Vendor dependencies for 0.3.0 release

This commit is contained in:
2025-09-27 10:29:08 -05:00
parent 0c8d39d483
commit 82ab7f317b
26803 changed files with 16134934 additions and 0 deletions

View File

@@ -0,0 +1 @@
{"files":{"Cargo.lock":"c263a59b6069e774143e49fab935529964904365d0147015a59d1448e2709613","Cargo.toml":"aa805fe0313c77a8c16e60776bf605576d14f14691dfe34242a6743a2ef1bc2c","LICENSE-APACHE":"a6cba85bc92e0cff7a450b1d873c0eaa2e9fc96bf472df0247a26bec77bf3ff9","LICENSE-MIT":"508a77d2e7b51d98adeed32648ad124b7b30241a8e70b2e72c99f92d8e5874d1","README.md":"359d89ad3b0b29812814315860380018f5b3d579c427bff0d88816b7729f1786","src/components.rs":"dfb259dc2e16529f9c484cb4122c667ddceb7c76da0eb8cc36b0311ae39180e9","src/dynamic_scene.rs":"3847d5dcb04a85441a35f327a1053dabf5c2151c0bf44bc339002dc31732df8c","src/dynamic_scene_builder.rs":"f7272113a12eed95648d444ea9491cc255ebdef13f876217a25692e25a70b3e9","src/lib.rs":"9972cf4350d507990c9b3153f0556a855539bab5a780b6b03cef4cecde03345d","src/reflect_utils.rs":"592f44feb72e14942c6ca47541f76fe3ad10d80b3485d8371be92fe34d482f24","src/scene.rs":"30ef7ce26d21a955eb41a090990bf7bd01a3221089adecf585d9fd63131bcc37","src/scene_filter.rs":"ed62cb6cc04e225da0b319a8f5e45701ac3cf81f111f724865ed6f3e0fe3229f","src/scene_loader.rs":"06d33239fa6dd62537bb30adb04578d181617d4c20d751351b247742d007a1d5","src/scene_spawner.rs":"519b6f0cb64c29c91b3a59425477f618bee16ec86344c4793f0818b9ed91c27e","src/serde.rs":"28a8e91538469f1a2d60e27b9008cb6ad8fff5cfee2b8f3601f863ae373fa802"},"package":"5c52ca165200995fe8afd2a1a6c03e4ffee49198a1d4653d32240ea7f217d4ab"}

3260
vendor/bevy_scene/Cargo.lock generated vendored Normal file

File diff suppressed because it is too large Load Diff

148
vendor/bevy_scene/Cargo.toml vendored Normal file
View File

@@ -0,0 +1,148 @@
# THIS FILE IS AUTOMATICALLY GENERATED BY CARGO
#
# When uploading crates to the registry Cargo will automatically
# "normalize" Cargo.toml files for maximal compatibility
# with all versions of Cargo and also rewrite `path` dependencies
# to registry (e.g., crates.io) dependencies.
#
# If you are reading this file be aware that the original Cargo.toml
# will likely look very different (and much more reasonable).
# See Cargo.toml.orig for the original contents.
[package]
edition = "2024"
name = "bevy_scene"
version = "0.16.1"
build = false
autolib = false
autobins = false
autoexamples = false
autotests = false
autobenches = false
description = "Provides scene functionality for Bevy Engine"
homepage = "https://bevyengine.org"
readme = "README.md"
keywords = ["bevy"]
license = "MIT OR Apache-2.0"
repository = "https://github.com/bevyengine/bevy"
resolver = "2"
[package.metadata.docs.rs]
all-features = true
rustdoc-args = [
"-Zunstable-options",
"--generate-link-to-definition",
]
[features]
default = ["serialize"]
serialize = [
"dep:serde",
"uuid/serde",
"bevy_ecs/serialize",
"bevy_platform/serialize",
]
[lib]
name = "bevy_scene"
path = "src/lib.rs"
[dependencies.bevy_app]
version = "0.16.1"
[dependencies.bevy_asset]
version = "0.16.1"
[dependencies.bevy_derive]
version = "0.16.1"
[dependencies.bevy_ecs]
version = "0.16.1"
[dependencies.bevy_platform]
version = "0.16.1"
features = ["std"]
default-features = false
[dependencies.bevy_reflect]
version = "0.16.1"
[dependencies.bevy_render]
version = "0.16.1"
optional = true
[dependencies.bevy_transform]
version = "0.16.1"
[dependencies.bevy_utils]
version = "0.16.1"
[dependencies.derive_more]
version = "1"
features = ["from"]
default-features = false
[dependencies.serde]
version = "1.0"
features = ["derive"]
optional = true
[dependencies.thiserror]
version = "2"
default-features = false
[dependencies.uuid]
version = "1.13.1"
features = ["v4"]
[dev-dependencies.bincode]
version = "2.0"
features = ["serde"]
[dev-dependencies.postcard]
version = "1.0"
features = ["alloc"]
[dev-dependencies.rmp-serde]
version = "1.1"
[target.'cfg(target_arch = "wasm32")'.dependencies.uuid]
version = "1.13.1"
features = ["js"]
default-features = false
[lints.clippy]
alloc_instead_of_core = "warn"
allow_attributes = "warn"
allow_attributes_without_reason = "warn"
doc_markdown = "warn"
manual_let_else = "warn"
match_same_arms = "warn"
needless_lifetimes = "allow"
nonstandard_macro_braces = "warn"
print_stderr = "warn"
print_stdout = "warn"
ptr_as_ptr = "warn"
ptr_cast_constness = "warn"
redundant_closure_for_method_calls = "warn"
redundant_else = "warn"
ref_as_ptr = "warn"
semicolon_if_nothing_returned = "warn"
std_instead_of_alloc = "warn"
std_instead_of_core = "warn"
too_long_first_doc_paragraph = "allow"
too_many_arguments = "allow"
type_complexity = "allow"
undocumented_unsafe_blocks = "warn"
unwrap_or_default = "warn"
[lints.rust]
missing_docs = "warn"
unsafe_code = "deny"
unsafe_op_in_unsafe_fn = "warn"
unused_qualifications = "warn"
[lints.rust.unexpected_cfgs]
level = "warn"
priority = 0
check-cfg = ["cfg(docsrs_dep)"]

176
vendor/bevy_scene/LICENSE-APACHE vendored Normal file
View File

@@ -0,0 +1,176 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS

19
vendor/bevy_scene/LICENSE-MIT vendored Normal file
View File

@@ -0,0 +1,19 @@
MIT License
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

7
vendor/bevy_scene/README.md vendored Normal file
View File

@@ -0,0 +1,7 @@
# 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_scene.svg)](https://crates.io/crates/bevy_scene)
[![Downloads](https://img.shields.io/crates/d/bevy_scene.svg)](https://crates.io/crates/bevy_scene)
[![Docs](https://docs.rs/bevy_scene/badge.svg)](https://docs.rs/bevy_scene/latest/bevy_scene/)
[![Discord](https://img.shields.io/discord/691052431525675048.svg?label=&logo=discord&logoColor=ffffff&color=7389D8&labelColor=6A7EC2)](https://discord.gg/bevy)

27
vendor/bevy_scene/src/components.rs vendored Normal file
View File

@@ -0,0 +1,27 @@
use bevy_asset::Handle;
use bevy_derive::{Deref, DerefMut};
use bevy_ecs::{component::Component, prelude::ReflectComponent};
use bevy_reflect::{prelude::ReflectDefault, Reflect};
use bevy_transform::components::Transform;
use derive_more::derive::From;
#[cfg(feature = "bevy_render")]
use bevy_render::view::visibility::Visibility;
use crate::{DynamicScene, Scene};
/// 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.
#[derive(Component, Clone, Debug, Default, Deref, DerefMut, Reflect, PartialEq, Eq, From)]
#[reflect(Component, Default, Debug, PartialEq, Clone)]
#[require(Transform)]
#[cfg_attr(feature = "bevy_render", require(Visibility))]
pub struct SceneRoot(pub Handle<Scene>);
/// 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.
#[derive(Component, Clone, Debug, Default, Deref, DerefMut, Reflect, PartialEq, Eq, From)]
#[reflect(Component, Default, Debug, PartialEq, Clone)]
#[require(Transform)]
#[cfg_attr(feature = "bevy_render", require(Visibility))]
pub struct DynamicSceneRoot(pub Handle<DynamicScene>);

399
vendor/bevy_scene/src/dynamic_scene.rs vendored Normal file
View File

@@ -0,0 +1,399 @@
use crate::{DynamicSceneBuilder, Scene, SceneSpawnError};
use bevy_asset::Asset;
use bevy_ecs::reflect::{ReflectMapEntities, ReflectResource};
use bevy_ecs::{
entity::{Entity, EntityHashMap, SceneEntityMapper},
reflect::{AppTypeRegistry, ReflectComponent},
world::World,
};
use bevy_reflect::{PartialReflect, TypePath};
use crate::reflect_utils::clone_reflect_value;
use bevy_ecs::component::ComponentCloneBehavior;
use bevy_ecs::relationship::RelationshipHookMode;
#[cfg(feature = "serialize")]
use {
crate::{ron, serde::SceneSerializer},
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`.
#[derive(Asset, TypePath, Default)]
pub struct DynamicScene {
/// Resources stored in the dynamic scene.
pub resources: Vec<Box<dyn PartialReflect>>,
/// Entities contained in the dynamic scene.
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).
///
/// Components that reference this entity must consistently use this identifier.
pub entity: Entity,
/// A vector of boxed components that belong to the given entity and
/// implement the [`PartialReflect`] trait.
pub components: Vec<Box<dyn PartialReflect>>,
}
impl DynamicScene {
/// Create a new dynamic scene from a given scene.
pub fn from_scene(scene: &Scene) -> Self {
Self::from_world(&scene.world)
}
/// Create a new dynamic scene from a given world.
pub fn from_world(world: &World) -> Self {
DynamicSceneBuilder::from_world(world)
.extract_entities(world.iter_entities().map(|entity| entity.id()))
.extract_resources()
.build()
}
/// 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
/// 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(
&self,
world: &mut World,
entity_map: &mut EntityHashMap<Entity>,
type_registry: &AppTypeRegistry,
) -> Result<(), SceneSpawnError> {
let type_registry = type_registry.read();
// First ensure that every entity in the scene has a corresponding world
// entity in the entity map.
for scene_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)
.or_insert_with(|| world.spawn_empty().id());
}
for scene_entity in &self.entities {
// Fetch the entity with the given entity id from the `entity_map`.
let entity = *entity_map
.get(&scene_entity.entity)
.expect("should have previously spawned an empty entity");
// Apply/ add each component to the given entity.
for component in &scene_entity.components {
let type_info = component.get_represented_type_info().ok_or_else(|| {
SceneSpawnError::NoRepresentedType {
type_path: component.reflect_type_path().to_string(),
}
})?;
let registration = type_registry.get(type_info.type_id()).ok_or_else(|| {
SceneSpawnError::UnregisteredButReflectedType {
type_path: type_info.type_path().to_string(),
}
})?;
let reflect_component =
registration.data::<ReflectComponent>().ok_or_else(|| {
SceneSpawnError::UnregisteredComponent {
type_path: type_info.type_path().to_string(),
}
})?;
{
let component_id = reflect_component.register_component(world);
// SAFETY: we registered the component above. the info exists
#[expect(unsafe_code, reason = "this is faster")]
let component_info =
unsafe { world.components().get_info_unchecked(component_id) };
if *component_info.clone_behavior() == ComponentCloneBehavior::Ignore {
continue;
}
}
SceneEntityMapper::world_scope(entity_map, world, |world, mapper| {
reflect_component.apply_or_insert_mapped(
&mut world.entity_mut(entity),
component.as_partial_reflect(),
&type_registry,
mapper,
RelationshipHookMode::Skip,
);
});
}
}
// Insert resources after all entities have been added to the world.
// 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 {
type_path: resource.reflect_type_path().to_string(),
}
})?;
let registration = type_registry.get(type_info.type_id()).ok_or_else(|| {
SceneSpawnError::UnregisteredButReflectedType {
type_path: type_info.type_path().to_string(),
}
})?;
let reflect_resource = registration.data::<ReflectResource>().ok_or_else(|| {
SceneSpawnError::UnregisteredResource {
type_path: type_info.type_path().to_string(),
}
})?;
// If this component references entities in the scene, update
// them to the entities in the world.
let mut cloned_resource;
let partial_reflect_resource = if let Some(map_entities) =
registration.data::<ReflectMapEntities>()
{
cloned_resource = clone_reflect_value(resource.as_partial_reflect(), registration);
SceneEntityMapper::world_scope(entity_map, world, |_, mapper| {
map_entities.map_entities(cloned_resource.as_partial_reflect_mut(), mapper);
});
cloned_resource.as_partial_reflect()
} else {
resource.as_partial_reflect()
};
// If the world already contains an instance of the given resource
// just apply the (possibly) new value, otherwise insert the resource
reflect_resource.apply_or_insert(world, partial_reflect_resource, &type_registry);
}
Ok(())
}
/// 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
/// 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> {
let registry = world.resource::<AppTypeRegistry>().clone();
self.write_to_world_with(world, entity_map, &registry)
}
// TODO: move to AssetSaver when it is implemented
/// Serialize this dynamic scene into the official Bevy scene 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`].
///
/// [`SceneLoader`]: crate::SceneLoader
/// [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 a given Rust data structure into rust object notation (ron).
#[cfg(feature = "serialize")]
pub fn serialize_ron<S>(serialize: S) -> Result<String, ron::Error>
where
S: Serialize,
{
let pretty_config = ron::ser::PrettyConfig::default()
.indentor(" ".to_string())
.new_line("\n".to_string());
ron::ser::to_string_pretty(&serialize, pretty_config)
}
#[cfg(test)]
mod tests {
use bevy_ecs::{
component::Component,
entity::{Entity, EntityHashMap, EntityMapper, MapEntities},
hierarchy::ChildOf,
reflect::{AppTypeRegistry, ReflectComponent, ReflectMapEntities, ReflectResource},
resource::Resource,
world::World,
};
use bevy_reflect::Reflect;
use crate::dynamic_scene::DynamicScene;
use crate::dynamic_scene_builder::DynamicSceneBuilder;
#[derive(Resource, Reflect, MapEntities, Debug)]
#[reflect(Resource, MapEntities)]
struct TestResource {
#[entities]
entity_a: Entity,
#[entities]
entity_b: Entity,
}
#[test]
fn resource_entity_map_maps_entities() {
let type_registry = AppTypeRegistry::default();
type_registry.write().register::<TestResource>();
let mut source_world = World::new();
source_world.insert_resource(type_registry.clone());
let original_entity_a = source_world.spawn_empty().id();
let original_entity_b = source_world.spawn_empty().id();
source_world.insert_resource(TestResource {
entity_a: original_entity_a,
entity_b: original_entity_b,
});
// Write the scene.
let scene = DynamicSceneBuilder::from_world(&source_world)
.extract_resources()
.extract_entity(original_entity_a)
.extract_entity(original_entity_b)
.build();
let mut entity_map = EntityHashMap::default();
let mut destination_world = World::new();
destination_world.insert_resource(type_registry);
scene
.write_to_world(&mut destination_world, &mut entity_map)
.unwrap();
let &from_entity_a = entity_map.get(&original_entity_a).unwrap();
let &from_entity_b = entity_map.get(&original_entity_b).unwrap();
let test_resource = destination_world.get_resource::<TestResource>().unwrap();
assert_eq!(from_entity_a, test_resource.entity_a);
assert_eq!(from_entity_b, test_resource.entity_b);
}
#[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.
// First, we create a simple world with a parent and a child relationship
let mut world = World::new();
world.init_resource::<AppTypeRegistry>();
world
.resource_mut::<AppTypeRegistry>()
.write()
.register::<ChildOf>();
let original_parent_entity = world.spawn_empty().id();
let original_child_entity = world.spawn_empty().id();
world
.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
// world to create another parent and child relationship
let scene = DynamicSceneBuilder::from_world(&world)
.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();
let &from_scene_parent_entity = entity_map.get(&original_parent_entity).unwrap();
let &from_scene_child_entity = entity_map.get(&original_child_entity).unwrap();
// We then add the parent from the scene as a child of the original child
// Hierarchy should look like:
// Original Parent <- Original Child <- Scene Parent <- Scene Child
world
.entity_mut(original_child_entity)
.add_child(from_scene_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.
// With [`bevy_ecs::hierarchy`], this can cause serious errors and malformed hierarchies.
scene.write_to_world(&mut world, &mut entity_map).unwrap();
assert_eq!(
original_parent_entity,
world
.get_entity(original_child_entity)
.unwrap()
.get::<ChildOf>()
.unwrap()
.parent(),
"something about reloading the scene is touching entities with the same scene Ids"
);
assert_eq!(
original_child_entity,
world
.get_entity(from_scene_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"
);
assert_eq!(
from_scene_parent_entity,
world
.get_entity(from_scene_child_entity)
.unwrap()
.get::<ChildOf>()
.expect("something is wrong with this test, and the scene 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"
);
}
// Regression test for https://github.com/bevyengine/bevy/issues/14300
// Fails before the fix in https://github.com/bevyengine/bevy/pull/15405
#[test]
fn no_panic_in_map_entities_after_pending_entity_in_hook() {
#[derive(Default, Component, Reflect)]
#[reflect(Component)]
struct A;
#[derive(Component, Reflect)]
#[reflect(Component)]
struct B(pub Entity);
impl MapEntities for B {
fn map_entities<E: EntityMapper>(&mut self, entity_mapper: &mut E) {
self.0 = entity_mapper.get_mapped(self.0);
}
}
let reg = AppTypeRegistry::default();
{
let mut reg_write = reg.write();
reg_write.register::<A>();
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 dst_world = World::new();
dst_world
.register_component_hooks::<A>()
.on_add(|mut world, _| {
world.commands().spawn_empty();
});
dst_world.insert_resource(reg.clone());
// Should not panic.
// 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
.write_to_world(&mut dst_world, &mut Default::default())
.unwrap();
}
}

View File

@@ -0,0 +1,738 @@
use core::any::TypeId;
use crate::reflect_utils::clone_reflect_value;
use crate::{DynamicEntity, DynamicScene, SceneFilter};
use alloc::collections::BTreeMap;
use bevy_ecs::{
component::{Component, ComponentId},
entity_disabling::DefaultQueryFilters,
prelude::Entity,
reflect::{AppTypeRegistry, ReflectComponent, ReflectResource},
resource::Resource,
world::World,
};
use bevy_reflect::PartialReflect;
use bevy_utils::default;
/// A [`DynamicScene`] builder, used to build a scene 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.
///
/// Extraction happens immediately and uses the filter as it exists during the time of extraction.
///
/// # Resource Extraction
///
/// 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.
///
/// Extraction happens immediately and uses the filter as it exists during the time of extraction.
///
/// # Entity Order
///
/// Extracted entities will always be stored in ascending order based on their [index](Entity::index).
/// This means that inserting `Entity(1v0)` then `Entity(0v0)` will always result in the entities
/// being ordered as `[Entity(0v0), Entity(1v0)]`.
///
/// # Example
/// ```
/// # use bevy_scene::DynamicSceneBuilder;
/// # use bevy_ecs::reflect::AppTypeRegistry;
/// # use bevy_ecs::{
/// # component::Component, prelude::Entity, query::With, reflect::ReflectComponent, world::World,
/// # };
/// # use bevy_reflect::Reflect;
/// # #[derive(Component, Reflect, Default, Eq, PartialEq, Debug)]
/// # #[reflect(Component)]
/// # struct ComponentA;
/// # let mut world = World::default();
/// # world.init_resource::<AppTypeRegistry>();
/// # let entity = world.spawn(ComponentA).id();
/// let dynamic_scene = DynamicSceneBuilder::from_world(&world).extract_entity(entity).build();
/// ```
///
/// [`Reflect`]: bevy_reflect::Reflect
pub struct DynamicSceneBuilder<'w> {
extracted_resources: BTreeMap<ComponentId, Box<dyn PartialReflect>>,
extracted_scene: BTreeMap<Entity, DynamicEntity>,
component_filter: SceneFilter,
resource_filter: SceneFilter,
original_world: &'w World,
}
impl<'w> DynamicSceneBuilder<'w> {
/// Prepare a builder that will extract entities and their component from the given [`World`].
pub fn from_world(world: &'w World) -> Self {
Self {
extracted_resources: default(),
extracted_scene: default(),
component_filter: SceneFilter::default(),
resource_filter: SceneFilter::default(),
original_world: world,
}
}
/// Specify a custom component [`SceneFilter`] to be used with this builder.
#[must_use]
pub fn with_component_filter(mut self, filter: SceneFilter) -> Self {
self.component_filter = filter;
self
}
/// Specify a custom resource [`SceneFilter`] to be used with this builder.
#[must_use]
pub fn with_resource_filter(mut self, filter: SceneFilter) -> Self {
self.resource_filter = filter;
self
}
/// Updates the filter to allow all component and resource types.
///
/// 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
}
/// Updates the filter to deny all component and resource types.
///
/// 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
}
/// Allows the given component type, `T`, to be included in the generated scene.
///
/// This method may be called multiple times for any number of components.
///
/// This is the inverse of [`deny_component`](Self::deny_component).
/// If `T` has already been denied, then it will be removed from the denylist.
#[must_use]
pub fn allow_component<T: Component>(mut self) -> Self {
self.component_filter = self.component_filter.allow::<T>();
self
}
/// Denies the given component type, `T`, from being included in the generated scene.
///
/// This method may be called multiple times for any number of components.
///
/// This is the inverse of [`allow_component`](Self::allow_component).
/// If `T` has already been allowed, then it will be removed from the allowlist.
#[must_use]
pub fn deny_component<T: Component>(mut self) -> Self {
self.component_filter = self.component_filter.deny::<T>();
self
}
/// Updates the filter to allow all component types.
///
/// This is useful for resetting the filter so that types may be selectively [denied].
///
/// [denied]: Self::deny_component
#[must_use]
pub fn allow_all_components(mut self) -> Self {
self.component_filter = SceneFilter::allow_all();
self
}
/// Updates the filter to deny all component types.
///
/// This is useful for resetting the filter so that types may be selectively [allowed].
///
/// [allowed]: Self::allow_component
#[must_use]
pub fn deny_all_components(mut self) -> Self {
self.component_filter = SceneFilter::deny_all();
self
}
/// Allows the given resource type, `T`, to be included in the generated scene.
///
/// This method may be called multiple times for any number of resources.
///
/// This is the inverse of [`deny_resource`](Self::deny_resource).
/// If `T` has already been denied, then it will be removed from the denylist.
#[must_use]
pub fn allow_resource<T: Resource>(mut self) -> Self {
self.resource_filter = self.resource_filter.allow::<T>();
self
}
/// Denies the given resource type, `T`, from being included in the generated scene.
///
/// This method may be called multiple times for any number of resources.
///
/// This is the inverse of [`allow_resource`](Self::allow_resource).
/// If `T` has already been allowed, then it will be removed from the allowlist.
#[must_use]
pub fn deny_resource<T: Resource>(mut self) -> Self {
self.resource_filter = self.resource_filter.deny::<T>();
self
}
/// Updates the filter to allow all resource types.
///
/// This is useful for resetting the filter so that types may be selectively [denied].
///
/// [denied]: Self::deny_resource
#[must_use]
pub fn allow_all_resources(mut self) -> Self {
self.resource_filter = SceneFilter::allow_all();
self
}
/// Updates the filter to deny all resource types.
///
/// This is useful for resetting the filter so that types may be selectively [allowed].
///
/// [allowed]: Self::allow_resource
#[must_use]
pub fn deny_all_resources(mut self) -> Self {
self.resource_filter = SceneFilter::deny_all();
self
}
/// Consume the builder, producing a [`DynamicScene`].
///
/// To make sure the dynamic scene doesn't contain entities without any components, call
/// [`Self::remove_empty_entities`] before building the scene.
#[must_use]
pub fn build(self) -> DynamicScene {
DynamicScene {
resources: self.extracted_resources.into_values().collect(),
entities: self.extracted_scene.into_values().collect(),
}
}
/// Extract one entity from the builder's [`World`].
///
/// Re-extracting an entity that was already extracted will have no effect.
#[must_use]
pub fn extract_entity(self, entity: Entity) -> Self {
self.extract_entities(core::iter::once(entity))
}
/// Despawns all entities with no components.
///
/// 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
.retain(|_, entity| !entity.components.is_empty());
self
}
/// Extract entities from the builder's [`World`].
///
/// Re-extracting an entity that was already extracted will have no effect.
///
/// To control which components are extracted, use the [`allow`] or
/// [`deny`] helper methods.
///
/// This method may be used to extract entities from a query:
/// ```
/// # use bevy_scene::DynamicSceneBuilder;
/// # use bevy_ecs::reflect::AppTypeRegistry;
/// # use bevy_ecs::{
/// # component::Component, prelude::Entity, query::With, reflect::ReflectComponent, world::World,
/// # };
/// # use bevy_reflect::Reflect;
/// #[derive(Component, Default, Reflect)]
/// #[reflect(Component)]
/// struct MyComponent;
///
/// # let mut world = World::default();
/// # world.init_resource::<AppTypeRegistry>();
/// # let _entity = world.spawn(MyComponent).id();
/// let mut query = world.query_filtered::<Entity, With<MyComponent>>();
///
/// let scene = DynamicSceneBuilder::from_world(&world)
/// .extract_entities(query.iter(&world))
/// .build();
/// ```
///
/// Note that components extracted from queried entities must still pass through the filter if one is set.
///
/// [`allow`]: Self::allow_component
/// [`deny`]: Self::deny_component
#[must_use]
pub fn extract_entities(mut self, entities: impl Iterator<Item = Entity>) -> Self {
let type_registry = self.original_world.resource::<AppTypeRegistry>().read();
for entity in entities {
if self.extracted_scene.contains_key(&entity) {
continue;
}
let mut entry = DynamicEntity {
entity,
components: Vec::new(),
};
let original_entity = self.original_world.entity(entity);
for component_id in original_entity.archetype().components() {
let mut extract_and_push = || {
let type_id = self
.original_world
.components()
.get_info(component_id)?
.type_id()?;
let is_denied = self.component_filter.is_denied_by_id(type_id);
if is_denied {
// Component is either in the denylist or _not_ in the allowlist
return None;
}
let type_registration = type_registry.get(type_id)?;
let component = type_registration
.data::<ReflectComponent>()?
.reflect(original_entity)?;
let component =
clone_reflect_value(component.as_partial_reflect(), type_registration);
entry.components.push(component);
Some(())
};
extract_and_push();
}
self.extracted_scene.insert(entity, entry);
}
self
}
/// Extract resources from the builder's [`World`].
///
/// Re-extracting a resource that was already extracted will have no effect.
///
/// To control which resources are extracted, use the [`allow_resource`] or
/// [`deny_resource`] helper methods.
///
/// ```
/// # use bevy_scene::DynamicSceneBuilder;
/// # use bevy_ecs::reflect::AppTypeRegistry;
/// # use bevy_ecs::prelude::{ReflectResource, Resource, World};
/// # use bevy_reflect::Reflect;
/// #[derive(Resource, Default, Reflect)]
/// #[reflect(Resource)]
/// struct MyResource;
///
/// # let mut world = World::default();
/// # world.init_resource::<AppTypeRegistry>();
/// world.insert_resource(MyResource);
///
/// let mut builder = DynamicSceneBuilder::from_world(&world).extract_resources();
/// let scene = builder.build();
/// ```
///
/// [`allow_resource`]: Self::allow_resource
/// [`deny_resource`]: Self::deny_resource
#[must_use]
pub fn extract_resources(mut self) -> Self {
// Don't extract the DefaultQueryFilters resource
let original_world_dqf_id = self
.original_world
.components()
.get_resource_id(TypeId::of::<DefaultQueryFilters>());
let type_registry = self.original_world.resource::<AppTypeRegistry>().read();
for (component_id, _) in self.original_world.storages().resources.iter() {
if Some(component_id) == original_world_dqf_id {
continue;
}
let mut extract_and_push = || {
let type_id = self
.original_world
.components()
.get_info(component_id)?
.type_id()?;
let is_denied = self.resource_filter.is_denied_by_id(type_id);
if is_denied {
// Resource is either in the denylist or _not_ in the allowlist
return None;
}
let type_registration = type_registry.get(type_id)?;
let resource = type_registration
.data::<ReflectResource>()?
.reflect(self.original_world)
.ok()?;
let resource =
clone_reflect_value(resource.as_partial_reflect(), type_registration);
self.extracted_resources.insert(component_id, resource);
Some(())
};
extract_and_push();
}
drop(type_registry);
self
}
}
#[cfg(test)]
mod tests {
use bevy_ecs::{
component::Component,
prelude::{Entity, Resource},
query::With,
reflect::{AppTypeRegistry, ReflectComponent, ReflectResource},
world::World,
};
use bevy_reflect::Reflect;
use super::DynamicSceneBuilder;
#[derive(Component, Reflect, Default, Eq, PartialEq, Debug)]
#[reflect(Component)]
struct ComponentA;
#[derive(Component, Reflect, Default, Eq, PartialEq, Debug)]
#[reflect(Component)]
struct ComponentB;
#[derive(Resource, Reflect, Default, Eq, PartialEq, Debug)]
#[reflect(Resource)]
struct ResourceA;
#[derive(Resource, Reflect, Default, Eq, PartialEq, Debug)]
#[reflect(Resource)]
struct ResourceB;
#[test]
fn extract_one_entity() {
let mut world = World::default();
let atr = AppTypeRegistry::default();
atr.write().register::<ComponentA>();
world.insert_resource(atr);
let entity = world.spawn((ComponentA, ComponentB)).id();
let scene = DynamicSceneBuilder::from_world(&world)
.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>());
}
#[test]
fn extract_one_entity_twice() {
let mut world = World::default();
let atr = AppTypeRegistry::default();
atr.write().register::<ComponentA>();
world.insert_resource(atr);
let entity = world.spawn((ComponentA, ComponentB)).id();
let scene = DynamicSceneBuilder::from_world(&world)
.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>());
}
#[test]
fn extract_one_entity_two_components() {
let mut world = World::default();
let atr = AppTypeRegistry::default();
{
let mut register = atr.write();
register.register::<ComponentA>();
register.register::<ComponentB>();
}
world.insert_resource(atr);
let entity = world.spawn((ComponentA, ComponentB)).id();
let scene = DynamicSceneBuilder::from_world(&world)
.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>());
}
#[test]
fn extract_entity_order() {
let mut world = World::default();
world.init_resource::<AppTypeRegistry>();
// Spawn entities in order
let entity_a = world.spawn_empty().id();
let entity_b = world.spawn_empty().id();
let entity_c = world.spawn_empty().id();
let entity_d = world.spawn_empty().id();
// Insert entities out of order
let builder = DynamicSceneBuilder::from_world(&world)
.extract_entity(entity_b)
.extract_entities([entity_d, entity_a].into_iter())
.extract_entity(entity_c);
let mut entities = builder.build().entities.into_iter();
// Assert entities are ordered
assert_eq!(entity_a, entities.next().map(|e| e.entity).unwrap());
assert_eq!(entity_b, entities.next().map(|e| e.entity).unwrap());
assert_eq!(entity_c, entities.next().map(|e| e.entity).unwrap());
assert_eq!(entity_d, entities.next().map(|e| e.entity).unwrap());
}
#[test]
fn extract_query() {
let mut world = World::default();
let atr = AppTypeRegistry::default();
{
let mut register = atr.write();
register.register::<ComponentA>();
register.register::<ComponentB>();
}
world.insert_resource(atr);
let entity_a_b = world.spawn((ComponentA, ComponentB)).id();
let entity_a = world.spawn(ComponentA).id();
let _entity_b = world.spawn(ComponentB).id();
let mut query = world.query_filtered::<Entity, With<ComponentA>>();
let scene = DynamicSceneBuilder::from_world(&world)
.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_b, entity_a]);
}
#[test]
fn remove_componentless_entity() {
let mut world = World::default();
let atr = AppTypeRegistry::default();
atr.write().register::<ComponentA>();
world.insert_resource(atr);
let entity_a = world.spawn(ComponentA).id();
let entity_b = world.spawn(ComponentB).id();
let scene = DynamicSceneBuilder::from_world(&world)
.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);
}
#[test]
fn extract_one_resource() {
let mut world = World::default();
let atr = AppTypeRegistry::default();
atr.write().register::<ResourceA>();
world.insert_resource(atr);
world.insert_resource(ResourceA);
let scene = DynamicSceneBuilder::from_world(&world)
.extract_resources()
.build();
assert_eq!(scene.resources.len(), 1);
assert!(scene.resources[0].represents::<ResourceA>());
}
#[test]
fn extract_one_resource_twice() {
let mut world = World::default();
let atr = AppTypeRegistry::default();
atr.write().register::<ResourceA>();
world.insert_resource(atr);
world.insert_resource(ResourceA);
let scene = DynamicSceneBuilder::from_world(&world)
.extract_resources()
.extract_resources()
.build();
assert_eq!(scene.resources.len(), 1);
assert!(scene.resources[0].represents::<ResourceA>());
}
#[test]
fn should_extract_allowed_components() {
let mut world = World::default();
let atr = AppTypeRegistry::default();
{
let mut register = atr.write();
register.register::<ComponentA>();
register.register::<ComponentB>();
}
world.insert_resource(atr);
let entity_a_b = world.spawn((ComponentA, ComponentB)).id();
let entity_a = world.spawn(ComponentA).id();
let entity_b = world.spawn(ComponentB).id();
let scene = DynamicSceneBuilder::from_world(&world)
.allow_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::<ComponentA>());
assert!(scene.entities[1].components[0].represents::<ComponentA>());
assert_eq!(scene.entities[2].components.len(), 0);
}
#[test]
fn should_not_extract_denied_components() {
let mut world = World::default();
let atr = AppTypeRegistry::default();
{
let mut register = atr.write();
register.register::<ComponentA>();
register.register::<ComponentB>();
}
world.insert_resource(atr);
let entity_a_b = world.spawn((ComponentA, ComponentB)).id();
let entity_a = world.spawn(ComponentA).id();
let entity_b = world.spawn(ComponentB).id();
let scene = DynamicSceneBuilder::from_world(&world)
.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>());
}
#[test]
fn should_extract_allowed_resources() {
let mut world = World::default();
let atr = AppTypeRegistry::default();
{
let mut register = atr.write();
register.register::<ResourceA>();
register.register::<ResourceB>();
}
world.insert_resource(atr);
world.insert_resource(ResourceA);
world.insert_resource(ResourceB);
let scene = DynamicSceneBuilder::from_world(&world)
.allow_resource::<ResourceA>()
.extract_resources()
.build();
assert_eq!(scene.resources.len(), 1);
assert!(scene.resources[0].represents::<ResourceA>());
}
#[test]
fn should_not_extract_denied_resources() {
let mut world = World::default();
let atr = AppTypeRegistry::default();
{
let mut register = atr.write();
register.register::<ResourceA>();
register.register::<ResourceB>();
}
world.insert_resource(atr);
world.insert_resource(ResourceA);
world.insert_resource(ResourceB);
let scene = DynamicSceneBuilder::from_world(&world)
.deny_resource::<ResourceA>()
.extract_resources()
.build();
assert_eq!(scene.resources.len(), 1);
assert!(scene.resources[0].represents::<ResourceB>());
}
#[test]
fn should_use_from_reflect() {
#[derive(Resource, Component, Reflect)]
#[reflect(Resource, Component)]
struct SomeType(i32);
let mut world = World::default();
let atr = AppTypeRegistry::default();
{
let mut register = atr.write();
register.register::<SomeType>();
}
world.insert_resource(atr);
world.insert_resource(SomeType(123));
let entity = world.spawn(SomeType(123)).id();
let scene = DynamicSceneBuilder::from_world(&world)
.extract_resources()
.extract_entities(vec![entity].into_iter())
.build();
let component = &scene.entities[0].components[0];
assert!(component
.try_as_reflect()
.expect("component should be concrete due to `FromReflect`")
.is::<SomeType>());
let resource = &scene.resources[0];
assert!(resource
.try_as_reflect()
.expect("resource should be concrete due to `FromReflect`")
.is::<SomeType>());
}
}

109
vendor/bevy_scene/src/lib.rs vendored Normal file
View File

@@ -0,0 +1,109 @@
#![cfg_attr(docsrs, feature(doc_auto_cfg))]
#![doc(
html_logo_url = "https://bevyengine.org/assets/icon.png",
html_favicon_url = "https://bevyengine.org/assets/icon.png"
)]
//! Provides scene 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,
//! 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 reflect_utils;
mod scene;
mod scene_filter;
mod scene_loader;
mod scene_spawner;
#[cfg(feature = "serialize")]
pub mod serde;
/// Rusty Object Notation, a crate used to serialize and deserialize bevy scenes.
pub use bevy_asset::ron;
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::*;
/// The scene 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,
};
}
use bevy_app::prelude::*;
#[cfg(feature = "serialize")]
use {bevy_asset::AssetApp, bevy_ecs::schedule::IntoScheduleConfigs};
/// Plugin that provides scene functionality to an [`App`].
#[derive(Default)]
pub struct ScenePlugin;
#[cfg(feature = "serialize")]
impl Plugin for ScenePlugin {
fn build(&self, app: &mut App) {
app.init_asset::<DynamicScene>()
.init_asset::<Scene>()
.init_asset_loader::<SceneLoader>()
.init_resource::<SceneSpawner>()
.register_type::<SceneRoot>()
.register_type::<DynamicSceneRoot>()
.add_systems(SpawnScene, (scene_spawner, scene_spawner_system).chain());
// Register component hooks for DynamicSceneRoot
app.world_mut()
.register_component_hooks::<DynamicSceneRoot>()
.on_remove(|mut world, context| {
let Some(handle) = world.get::<DynamicSceneRoot>(context.entity) else {
return;
};
let id = handle.id();
if let Some(&SceneInstance(scene_instance)) =
world.get::<SceneInstance>(context.entity)
{
let Some(mut scene_spawner) = world.get_resource_mut::<SceneSpawner>() else {
return;
};
if let Some(instance_ids) = scene_spawner.spawned_dynamic_scenes.get_mut(&id) {
instance_ids.remove(&scene_instance);
}
scene_spawner.unregister_instance(scene_instance);
}
});
// Register component hooks for SceneRoot
app.world_mut()
.register_component_hooks::<SceneRoot>()
.on_remove(|mut world, context| {
if let Some(&SceneInstance(scene_instance)) =
world.get::<SceneInstance>(context.entity)
{
let Some(mut scene_spawner) = world.get_resource_mut::<SceneSpawner>() else {
return;
};
scene_spawner.unregister_instance(scene_instance);
}
});
}
}
#[cfg(not(feature = "serialize"))]
impl Plugin for ScenePlugin {
fn build(&self, _: &mut App) {}
}

25
vendor/bevy_scene/src/reflect_utils.rs vendored Normal file
View File

@@ -0,0 +1,25 @@
use bevy_reflect::{PartialReflect, ReflectFromReflect, TypeRegistration};
/// Attempts to clone a [`PartialReflect`] value using various methods.
///
/// This first attempts to clone via [`PartialReflect::reflect_clone`].
/// then falls back to [`ReflectFromReflect::from_reflect`],
/// and finally [`PartialReflect::to_dynamic`] if the first two methods fail.
///
/// This helps ensure that the original type and type data is retained,
/// and only returning a dynamic type if all other methods fail.
pub(super) fn clone_reflect_value(
value: &dyn PartialReflect,
type_registration: &TypeRegistration,
) -> Box<dyn PartialReflect> {
value
.reflect_clone()
.map(PartialReflect::into_partial_reflect)
.unwrap_or_else(|_| {
type_registration
.data::<ReflectFromReflect>()
.and_then(|fr| fr.from_reflect(value.as_partial_reflect()))
.map(PartialReflect::into_partial_reflect)
.unwrap_or_else(|| value.to_dynamic())
})
}

171
vendor/bevy_scene/src/scene.rs vendored Normal file
View File

@@ -0,0 +1,171 @@
use core::any::TypeId;
use crate::reflect_utils::clone_reflect_value;
use crate::{DynamicScene, SceneSpawnError};
use bevy_asset::Asset;
use bevy_ecs::{
component::ComponentCloneBehavior,
entity::{Entity, EntityHashMap, SceneEntityMapper},
entity_disabling::DefaultQueryFilters,
reflect::{AppTypeRegistry, ReflectComponent, ReflectResource},
relationship::RelationshipHookMode,
world::World,
};
use bevy_reflect::TypePath;
/// 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.
#[derive(Asset, TypePath, Debug)]
pub struct Scene {
/// The world of the scene, containing its entities and resources.
pub world: World,
}
impl Scene {
/// Creates a new scene 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,
type_registry: &AppTypeRegistry,
) -> Result<Scene, SceneSpawnError> {
let mut world = World::new();
let mut entity_map = EntityHashMap::default();
dynamic_scene.write_to_world_with(&mut world, &mut entity_map, type_registry)?;
Ok(Self { world })
}
/// Clone the scene.
///
/// This method will return a [`SceneSpawnError`] 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> {
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)?;
Ok(Self { world: new_world })
}
/// 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
/// 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> {
let type_registry = type_registry.read();
let self_dqf_id = self
.world
.components()
.get_resource_id(TypeId::of::<DefaultQueryFilters>());
// Resources archetype
for (component_id, resource_data) in self.world.storages().resources.iter() {
if Some(component_id) == self_dqf_id {
continue;
}
if !resource_data.is_present() {
continue;
}
let component_info = self
.world
.components()
.get_info(component_id)
.expect("component_ids in archetypes should have ComponentInfo");
let type_id = component_info
.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().to_string(),
})?;
let reflect_resource = registration.data::<ReflectResource>().ok_or_else(|| {
SceneSpawnError::UnregisteredResource {
type_path: registration.type_info().type_path().to_string(),
}
})?;
reflect_resource.copy(&self.world, world, &type_registry);
}
// Ensure that all scene 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() {
for scene_entity in archetype.entities() {
entity_map
.entry(scene_entity.id())
.or_insert_with(|| world.spawn_empty().id());
}
}
for archetype in self.world.archetypes().iter() {
for scene_entity in archetype.entities() {
let entity = *entity_map
.get(&scene_entity.id())
.expect("should have previously spawned an entity");
for component_id in archetype.components() {
let component_info = self
.world
.components()
.get_info(component_id)
.expect("component_ids in archetypes should have ComponentInfo");
if *component_info.clone_behavior() == ComponentCloneBehavior::Ignore {
continue;
}
let registration = type_registry
.get(component_info.type_id().unwrap())
.ok_or_else(|| SceneSpawnError::UnregisteredType {
std_type_name: component_info.name().to_string(),
})?;
let reflect_component =
registration.data::<ReflectComponent>().ok_or_else(|| {
SceneSpawnError::UnregisteredComponent {
type_path: registration.type_info().type_path().to_string(),
}
})?;
let Some(component) = reflect_component
.reflect(self.world.entity(scene_entity.id()))
.map(|component| {
clone_reflect_value(component.as_partial_reflect(), registration)
})
else {
continue;
};
// If this component references entities in the scene,
// update them to the entities in the world.
SceneEntityMapper::world_scope(entity_map, world, |world, mapper| {
reflect_component.apply_or_insert_mapped(
&mut world.entity_mut(entity),
component.as_partial_reflect(),
&type_registry,
mapper,
RelationshipHookMode::Skip,
);
});
}
}
}
Ok(())
}
}

275
vendor/bevy_scene/src/scene_filter.rs vendored Normal file
View File

@@ -0,0 +1,275 @@
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`].
///
/// 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`.
///
/// [`DynamicScene`]: crate::DynamicScene
/// [components]: bevy_ecs::prelude::Component
/// [resources]: bevy_ecs::prelude::Resource
#[derive(Default, Debug, Clone, PartialEq, Eq)]
pub enum SceneFilter {
/// Represents an unset filter.
///
/// This is the equivalent of an empty [`Denylist`] or an [`Allowlist`] containing every type—
/// essentially, all types are permissible.
///
/// [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
#[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`].
///
/// [`DynamicScene`]: crate::DynamicScene
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`].
///
/// [`DynamicScene`]: crate::DynamicScene
Denylist(HashSet<TypeId>),
}
impl SceneFilter {
/// Creates a filter where all types are allowed.
///
/// This is the equivalent of creating an empty [`Denylist`].
///
/// [`Denylist`]: SceneFilter::Denylist
pub fn allow_all() -> Self {
Self::Denylist(HashSet::default())
}
/// Creates a filter where all types are denied.
///
/// This is the equivalent of creating an empty [`Allowlist`].
///
/// [`Allowlist`]: SceneFilter::Allowlist
pub fn deny_all() -> Self {
Self::Allowlist(HashSet::default())
}
/// Allow the given type, `T`.
///
/// If this filter is already set as a [`Denylist`],
/// then the given type will be removed from the denied set.
///
/// If this filter is [`Unset`], then it will be completely replaced by a new [`Allowlist`].
///
/// [`Denylist`]: SceneFilter::Denylist
/// [`Unset`]: SceneFilter::Unset
/// [`Allowlist`]: SceneFilter::Allowlist
#[must_use]
pub fn allow<T: Any>(self) -> Self {
self.allow_by_id(TypeId::of::<T>())
}
/// Allow the given type.
///
/// If this filter is already set as a [`Denylist`],
/// then the given type will be removed from the denied set.
///
/// If this filter is [`Unset`], then it will be completely replaced by a new [`Allowlist`].
///
/// [`Denylist`]: SceneFilter::Denylist
/// [`Unset`]: SceneFilter::Unset
/// [`Allowlist`]: SceneFilter::Allowlist
#[must_use]
pub fn allow_by_id(mut self, type_id: TypeId) -> Self {
match &mut self {
Self::Unset => {
self = Self::Allowlist([type_id].into_iter().collect());
}
Self::Allowlist(list) => {
list.insert(type_id);
}
Self::Denylist(list) => {
list.remove(&type_id);
}
}
self
}
/// Deny the given type, `T`.
///
/// If this filter is already set as an [`Allowlist`],
/// then the given type will be removed from the allowed set.
///
/// If this filter is [`Unset`], then it will be completely replaced by a new [`Denylist`].
///
/// [`Allowlist`]: SceneFilter::Allowlist
/// [`Unset`]: SceneFilter::Unset
/// [`Denylist`]: SceneFilter::Denylist
#[must_use]
pub fn deny<T: Any>(self) -> Self {
self.deny_by_id(TypeId::of::<T>())
}
/// Deny the given type.
///
/// If this filter is already set as an [`Allowlist`],
/// then the given type will be removed from the allowed set.
///
/// If this filter is [`Unset`], then it will be completely replaced by a new [`Denylist`].
///
/// [`Allowlist`]: SceneFilter::Allowlist
/// [`Unset`]: SceneFilter::Unset
/// [`Denylist`]: SceneFilter::Denylist
#[must_use]
pub fn deny_by_id(mut self, type_id: TypeId) -> Self {
match &mut self {
Self::Unset => self = Self::Denylist([type_id].into_iter().collect()),
Self::Allowlist(list) => {
list.remove(&type_id);
}
Self::Denylist(list) => {
list.insert(type_id);
}
}
self
}
/// Returns true if the given type, `T`, is allowed by the filter.
///
/// If the filter is [`Unset`], this will always return `true`.
///
/// [`Unset`]: SceneFilter::Unset
pub fn is_allowed<T: Any>(&self) -> bool {
self.is_allowed_by_id(TypeId::of::<T>())
}
/// Returns true if the given type is allowed by the filter.
///
/// If the filter is [`Unset`], this will always return `true`.
///
/// [`Unset`]: SceneFilter::Unset
pub fn is_allowed_by_id(&self, type_id: TypeId) -> bool {
match self {
Self::Unset => true,
Self::Allowlist(list) => list.contains(&type_id),
Self::Denylist(list) => !list.contains(&type_id),
}
}
/// Returns true if the given type, `T`, is denied by the filter.
///
/// If the filter is [`Unset`], this will always return `false`.
///
/// [`Unset`]: SceneFilter::Unset
pub fn is_denied<T: Any>(&self) -> bool {
self.is_denied_by_id(TypeId::of::<T>())
}
/// Returns true if the given type is denied by the filter.
///
/// If the filter is [`Unset`], this will always return `false`.
///
/// [`Unset`]: SceneFilter::Unset
pub fn is_denied_by_id(&self, type_id: TypeId) -> bool {
!self.is_allowed_by_id(type_id)
}
/// Returns an iterator over the items in the filter.
///
/// If the filter is [`Unset`], this will return an empty iterator.
///
/// [`Unset`]: SceneFilter::Unset
pub fn iter(&self) -> Box<dyn ExactSizeIterator<Item = &TypeId> + '_> {
match self {
Self::Unset => Box::new(core::iter::empty()),
Self::Allowlist(list) | Self::Denylist(list) => Box::new(list.iter()),
}
}
/// Returns the number of items in the filter.
///
/// If the filter is [`Unset`], this will always return a length of zero.
///
/// [`Unset`]: SceneFilter::Unset
pub fn len(&self) -> usize {
match self {
Self::Unset => 0,
Self::Allowlist(list) | Self::Denylist(list) => list.len(),
}
}
/// Returns true if there are zero items in the filter.
///
/// If the filter is [`Unset`], this will always return `true`.
///
/// [`Unset`]: SceneFilter::Unset
pub fn is_empty(&self) -> bool {
match self {
Self::Unset => true,
Self::Allowlist(list) | Self::Denylist(list) => list.is_empty(),
}
}
}
impl IntoIterator for SceneFilter {
type Item = TypeId;
type IntoIter = IntoIter<TypeId>;
fn into_iter(self) -> Self::IntoIter {
match self {
Self::Unset => Default::default(),
Self::Allowlist(list) | Self::Denylist(list) => list.into_iter(),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn should_set_list_type_if_none() {
let filter = SceneFilter::Unset.allow::<i32>();
assert!(matches!(filter, SceneFilter::Allowlist(_)));
let filter = SceneFilter::Unset.deny::<i32>();
assert!(matches!(filter, SceneFilter::Denylist(_)));
}
#[test]
fn should_add_to_list() {
let filter = SceneFilter::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>();
assert_eq!(2, filter.len());
assert!(filter.is_denied::<i16>());
assert!(filter.is_denied::<i32>());
}
#[test]
fn should_remove_from_list() {
let filter = SceneFilter::default()
.allow::<i16>()
.allow::<i32>()
.deny::<i32>();
assert_eq!(1, filter.len());
assert!(filter.is_allowed::<i16>());
assert!(!filter.is_allowed::<i32>());
let filter = SceneFilter::default()
.deny::<i16>()
.deny::<i32>()
.allow::<i32>();
assert_eq!(1, filter.len());
assert!(filter.is_denied::<i16>());
assert!(!filter.is_denied::<i32>());
}
}

75
vendor/bevy_scene/src/scene_loader.rs vendored Normal file
View File

@@ -0,0 +1,75 @@
use crate::ron;
use bevy_ecs::{
reflect::AppTypeRegistry,
world::{FromWorld, World},
};
use bevy_reflect::TypeRegistryArc;
use thiserror::Error;
#[cfg(feature = "serialize")]
use {
crate::{serde::SceneDeserializer, DynamicScene},
bevy_asset::{io::Reader, AssetLoader, LoadContext},
serde::de::DeserializeSeed,
};
/// Asset loader for a Bevy dynamic scene (`.scn` / `.scn.ron`).
///
/// The loader handles assets serialized with [`DynamicScene::serialize`].
#[derive(Debug)]
pub struct SceneLoader {
#[cfg_attr(
not(feature = "serialize"),
expect(dead_code, reason = "only used with `serialize` feature")
)]
type_registry: TypeRegistryArc,
}
impl FromWorld for SceneLoader {
fn from_world(world: &mut World) -> Self {
let type_registry = world.resource::<AppTypeRegistry>();
SceneLoader {
type_registry: type_registry.0.clone(),
}
}
}
/// Possible errors that can be produced by [`SceneLoader`]
#[non_exhaustive]
#[derive(Debug, Error)]
pub enum SceneLoaderError {
/// An [IO Error](std::io::Error)
#[error("Error while trying to read the scene file: {0}")]
Io(#[from] std::io::Error),
/// A [RON Error](ron::error::SpannedError)
#[error("Could not parse RON: {0}")]
RonSpannedError(#[from] ron::error::SpannedError),
}
#[cfg(feature = "serialize")]
impl AssetLoader for SceneLoader {
type Asset = DynamicScene;
type Settings = ();
type Error = SceneLoaderError;
async fn load(
&self,
reader: &mut dyn Reader,
_settings: &(),
_load_context: &mut LoadContext<'_>,
) -> Result<Self::Asset, Self::Error> {
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 {
type_registry: &self.type_registry.read(),
};
Ok(scene_deserializer
.deserialize(&mut deserializer)
.map_err(|e| deserializer.span_error(e))?)
}
fn extensions(&self) -> &[&str] {
&["scn", "scn.ron"]
}
}

945
vendor/bevy_scene/src/scene_spawner.rs vendored Normal file
View File

@@ -0,0 +1,945 @@
use crate::{DynamicScene, Scene};
use bevy_asset::{AssetEvent, AssetId, Assets, Handle};
use bevy_ecs::{
entity::{Entity, EntityHashMap},
event::{Event, EventCursor, Events},
hierarchy::ChildOf,
reflect::AppTypeRegistry,
resource::Resource,
world::{Mut, World},
};
use bevy_platform::collections::{HashMap, HashSet};
use bevy_reflect::Reflect;
use thiserror::Error;
use uuid::Uuid;
use crate::{DynamicSceneRoot, SceneRoot};
use bevy_derive::{Deref, DerefMut};
use bevy_ecs::{
change_detection::ResMut,
prelude::{Changed, Component, Without},
system::{Commands, Query},
};
/// Triggered on a scene's parent entity when [`crate::SceneInstance`] becomes ready to use.
///
/// See also [`Trigger`], [`SceneSpawner::instance_is_ready`].
///
/// [`Trigger`]: bevy_ecs::observer::Trigger
#[derive(Clone, Copy, Debug, Eq, PartialEq, Event, Reflect)]
#[reflect(Debug, PartialEq, Clone)]
pub struct SceneInstanceReady {
/// Instance which has been spawned.
pub instance_id: InstanceId,
}
/// Information about a scene instance.
#[derive(Debug)]
pub struct InstanceInfo {
/// Mapping of entities from the scene world to the instance world.
pub entity_map: EntityHashMap<Entity>,
}
/// Unique id identifying a scene instance.
#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash, Reflect)]
#[reflect(Debug, PartialEq, Hash, Clone)]
pub struct InstanceId(Uuid);
impl InstanceId {
fn new() -> Self {
InstanceId(Uuid::new_v4())
}
}
/// Handles spawning and despawning scenes in the world, either synchronously or batched through the [`scene_spawner_system`].
///
/// Synchronous methods: (Scene operations will take effect immediately)
/// - [`spawn_dynamic_sync`](Self::spawn_dynamic_sync)
/// - [`spawn_sync`](Self::spawn_sync)
/// - [`despawn_sync`](Self::despawn_sync)
/// - [`despawn_instance_sync`](Self::despawn_instance_sync)
/// - [`update_spawned_scenes`](Self::update_spawned_scenes)
/// - [`spawn_queued_scenes`](Self::spawn_queued_scenes)
/// - [`despawn_queued_scenes`](Self::despawn_queued_scenes)
/// - [`despawn_queued_instances`](Self::despawn_queued_instances)
///
/// Deferred methods: (Scene operations will be processed when the [`scene_spawner_system`] is run)
/// - [`spawn_dynamic`](Self::spawn_dynamic)
/// - [`spawn_dynamic_as_child`](Self::spawn_dynamic_as_child)
/// - [`spawn`](Self::spawn)
/// - [`spawn_as_child`](Self::spawn_as_child)
/// - [`despawn`](Self::despawn)
/// - [`despawn_instance`](Self::despawn_instance)
#[derive(Default, Resource)]
pub struct SceneSpawner {
pub(crate) spawned_dynamic_scenes: HashMap<AssetId<DynamicScene>, HashSet<InstanceId>>,
pub(crate) spawned_instances: HashMap<InstanceId, InstanceInfo>,
scene_asset_event_reader: EventCursor<AssetEvent<DynamicScene>>,
dynamic_scenes_to_spawn: Vec<(Handle<DynamicScene>, InstanceId, Option<Entity>)>,
scenes_to_spawn: Vec<(Handle<Scene>, InstanceId, Option<Entity>)>,
scenes_to_despawn: Vec<AssetId<DynamicScene>>,
instances_to_despawn: Vec<InstanceId>,
scenes_with_parent: Vec<(InstanceId, Entity)>,
instances_ready: Vec<(InstanceId, Option<Entity>)>,
}
/// Errors that can occur when spawning a scene.
#[derive(Error, Debug)]
pub enum SceneSpawnError {
/// Scene contains an unregistered component type.
#[error("scene contains the unregistered component `{type_path}`. consider adding `#[reflect(Component)]` to your type")]
UnregisteredComponent {
/// Type of the unregistered component.
type_path: String,
},
/// Scene contains an unregistered resource type.
#[error("scene contains the unregistered resource `{type_path}`. consider adding `#[reflect(Resource)]` to your type")]
UnregisteredResource {
/// Type of the unregistered resource.
type_path: String,
},
/// Scene contains an unregistered type.
#[error(
"scene contains the unregistered type `{std_type_name}`. \
consider reflecting it with `#[derive(Reflect)]` \
and registering the type using `app.register_type::<T>()`"
)]
UnregisteredType {
/// The [type name](std::any::type_name) for the unregistered type.
std_type_name: String,
},
/// Scene contains an unregistered type which has a `TypePath`.
#[error(
"scene contains the reflected type `{type_path}` but it was not found in the type registry. \
consider registering the type using `app.register_type::<T>()``"
)]
UnregisteredButReflectedType {
/// The unregistered type.
type_path: String,
},
/// Scene contains a proxy without a represented type.
#[error("scene contains dynamic type `{type_path}` without a represented type. consider changing this using `set_represented_type`.")]
NoRepresentedType {
/// The dynamic instance type.
type_path: String,
},
/// Dynamic scene with the given id does not exist.
#[error("scene does not exist")]
NonExistentScene {
/// Id of the non-existent dynamic scene.
id: AssetId<DynamicScene>,
},
/// Scene with the given id does not exist.
#[error("scene does not exist")]
NonExistentRealScene {
/// Id of the non-existent scene.
id: AssetId<Scene>,
},
}
impl SceneSpawner {
/// Schedule the spawn of a new instance of the provided dynamic scene.
pub fn spawn_dynamic(&mut self, id: impl Into<Handle<DynamicScene>>) -> InstanceId {
let instance_id = InstanceId::new();
self.dynamic_scenes_to_spawn
.push((id.into(), instance_id, None));
instance_id
}
/// Schedule the spawn of a new instance of the provided dynamic scene as a child of `parent`.
pub fn spawn_dynamic_as_child(
&mut self,
id: impl Into<Handle<DynamicScene>>,
parent: Entity,
) -> InstanceId {
let instance_id = InstanceId::new();
self.dynamic_scenes_to_spawn
.push((id.into(), instance_id, Some(parent)));
self.scenes_with_parent.push((instance_id, parent));
instance_id
}
/// Schedule the spawn of a new instance of the provided scene.
pub fn spawn(&mut self, id: impl Into<Handle<Scene>>) -> InstanceId {
let instance_id = InstanceId::new();
self.scenes_to_spawn.push((id.into(), instance_id, None));
instance_id
}
/// Schedule the spawn of a new instance of the provided scene as a child of `parent`.
pub fn spawn_as_child(&mut self, id: impl Into<Handle<Scene>>, parent: Entity) -> InstanceId {
let instance_id = InstanceId::new();
self.scenes_to_spawn
.push((id.into(), instance_id, Some(parent)));
self.scenes_with_parent.push((instance_id, parent));
instance_id
}
/// Schedule the despawn of all instances of the provided dynamic scene.
pub fn despawn(&mut self, id: impl Into<AssetId<DynamicScene>>) {
self.scenes_to_despawn.push(id.into());
}
/// Schedule the despawn of a scene instance, removing all its entities from the world.
///
/// Note: this will despawn _all_ entities associated with this instance, including those
/// that have been removed from the scene hierarchy. To despawn _only_ entities still in the hierarchy,
/// despawn the relevant root entity directly.
pub fn despawn_instance(&mut self, instance_id: InstanceId) {
self.instances_to_despawn.push(instance_id);
}
/// This will remove all records of this instance, without despawning any entities.
pub fn unregister_instance(&mut self, instance_id: InstanceId) {
self.spawned_instances.remove(&instance_id);
}
/// Immediately despawns all instances of a dynamic scene.
pub fn despawn_sync(
&mut self,
world: &mut World,
id: impl Into<AssetId<DynamicScene>>,
) -> Result<(), SceneSpawnError> {
if let Some(instance_ids) = self.spawned_dynamic_scenes.remove(&id.into()) {
for instance_id in instance_ids {
self.despawn_instance_sync(world, &instance_id);
}
}
Ok(())
}
/// Immediately despawns a scene instance, removing all its entities from the world.
pub fn despawn_instance_sync(&mut self, world: &mut World, instance_id: &InstanceId) {
if let Some(instance) = self.spawned_instances.remove(instance_id) {
for &entity in instance.entity_map.values() {
if let Ok(entity_mut) = world.get_entity_mut(entity) {
entity_mut.despawn();
};
}
}
}
/// Immediately spawns a new instance of the provided dynamic scene.
pub fn spawn_dynamic_sync(
&mut self,
world: &mut World,
id: impl Into<AssetId<DynamicScene>>,
) -> Result<InstanceId, SceneSpawnError> {
let mut entity_map = EntityHashMap::default();
let id = id.into();
Self::spawn_dynamic_internal(world, id, &mut entity_map)?;
let instance_id = InstanceId::new();
self.spawned_instances
.insert(instance_id, InstanceInfo { entity_map });
let spawned = self.spawned_dynamic_scenes.entry(id).or_default();
spawned.insert(instance_id);
Ok(instance_id)
}
fn spawn_dynamic_internal(
world: &mut World,
id: AssetId<DynamicScene>,
entity_map: &mut EntityHashMap<Entity>,
) -> Result<(), SceneSpawnError> {
world.resource_scope(|world, scenes: Mut<Assets<DynamicScene>>| {
let scene = scenes
.get(id)
.ok_or(SceneSpawnError::NonExistentScene { id })?;
scene.write_to_world(world, entity_map)
})
}
/// Immediately spawns a new instance of the provided scene.
pub fn spawn_sync(
&mut self,
world: &mut World,
id: impl Into<AssetId<Scene>>,
) -> Result<InstanceId, SceneSpawnError> {
let mut entity_map = EntityHashMap::default();
let id = id.into();
Self::spawn_sync_internal(world, id, &mut entity_map)?;
let instance_id = InstanceId::new();
self.spawned_instances
.insert(instance_id, InstanceInfo { entity_map });
Ok(instance_id)
}
fn spawn_sync_internal(
world: &mut World,
id: AssetId<Scene>,
entity_map: &mut EntityHashMap<Entity>,
) -> Result<(), SceneSpawnError> {
world.resource_scope(|world, scenes: Mut<Assets<Scene>>| {
let scene = scenes
.get(id)
.ok_or(SceneSpawnError::NonExistentRealScene { id })?;
scene.write_to_world_with(
world,
entity_map,
&world.resource::<AppTypeRegistry>().clone(),
)
})
}
/// Iterate through all instances of the provided scenes and update those immediately.
///
/// Useful for updating already spawned scene instances after their corresponding scene has been modified.
pub fn update_spawned_scenes(
&mut self,
world: &mut World,
scene_ids: &[AssetId<DynamicScene>],
) -> Result<(), SceneSpawnError> {
for id in scene_ids {
if let Some(spawned_instances) = self.spawned_dynamic_scenes.get(id) {
for instance_id in spawned_instances {
if let Some(instance_info) = self.spawned_instances.get_mut(instance_id) {
Self::spawn_dynamic_internal(world, *id, &mut instance_info.entity_map)?;
}
}
}
}
Ok(())
}
/// Immediately despawns all scenes scheduled for despawn by despawning their instances.
pub fn despawn_queued_scenes(&mut self, world: &mut World) -> Result<(), SceneSpawnError> {
let scenes_to_despawn = core::mem::take(&mut self.scenes_to_despawn);
for scene_handle in scenes_to_despawn {
self.despawn_sync(world, scene_handle)?;
}
Ok(())
}
/// Immediately despawns all scene instances scheduled for despawn.
pub fn despawn_queued_instances(&mut self, world: &mut World) {
let instances_to_despawn = core::mem::take(&mut self.instances_to_despawn);
for instance_id in instances_to_despawn {
self.despawn_instance_sync(world, &instance_id);
}
}
/// Immediately spawns all scenes scheduled for spawn.
pub fn spawn_queued_scenes(&mut self, world: &mut World) -> Result<(), SceneSpawnError> {
let scenes_to_spawn = core::mem::take(&mut self.dynamic_scenes_to_spawn);
for (handle, instance_id, parent) in scenes_to_spawn {
let mut entity_map = EntityHashMap::default();
match Self::spawn_dynamic_internal(world, handle.id(), &mut entity_map) {
Ok(_) => {
self.spawned_instances
.insert(instance_id, InstanceInfo { entity_map });
let spawned = self.spawned_dynamic_scenes.entry(handle.id()).or_default();
spawned.insert(instance_id);
// Scenes with parents need more setup before they are ready.
// See `set_scene_instance_parent_sync()`.
if parent.is_none() {
// We trigger `SceneInstanceReady` events after processing all scenes
// SceneSpawner may not be available in the observer.
self.instances_ready.push((instance_id, None));
}
}
Err(SceneSpawnError::NonExistentScene { .. }) => {
self.dynamic_scenes_to_spawn
.push((handle, instance_id, parent));
}
Err(err) => return Err(err),
}
}
let scenes_to_spawn = core::mem::take(&mut self.scenes_to_spawn);
for (scene_handle, instance_id, parent) in scenes_to_spawn {
let mut entity_map = EntityHashMap::default();
match Self::spawn_sync_internal(world, scene_handle.id(), &mut entity_map) {
Ok(_) => {
self.spawned_instances
.insert(instance_id, InstanceInfo { entity_map });
// Scenes with parents need more setup before they are ready.
// See `set_scene_instance_parent_sync()`.
if parent.is_none() {
// We trigger `SceneInstanceReady` events after processing all scenes
// SceneSpawner may not be available in the observer.
self.instances_ready.push((instance_id, None));
}
}
Err(SceneSpawnError::NonExistentRealScene { .. }) => {
self.scenes_to_spawn
.push((scene_handle, instance_id, parent));
}
Err(err) => return Err(err),
}
}
Ok(())
}
pub(crate) fn set_scene_instance_parent_sync(&mut self, world: &mut World) {
let scenes_with_parent = core::mem::take(&mut self.scenes_with_parent);
for (instance_id, parent) in scenes_with_parent {
if let Some(instance) = self.spawned_instances.get(&instance_id) {
for &entity in instance.entity_map.values() {
// Add the `ChildOf` component to the scene root, and update the `Children` component of
// the scene parent
if !world
.get_entity(entity)
.ok()
// This will filter only the scene root entity, as all other from the
// scene have a parent
// Entities that wouldn't exist anymore are also skipped
// this case shouldn't happen anyway
.is_none_or(|entity| entity.contains::<ChildOf>())
{
world.entity_mut(parent).add_child(entity);
}
}
// We trigger `SceneInstanceReady` events after processing all scenes
// SceneSpawner may not be available in the observer.
self.instances_ready.push((instance_id, Some(parent)));
} else {
self.scenes_with_parent.push((instance_id, parent));
}
}
}
fn trigger_scene_ready_events(&mut self, world: &mut World) {
for (instance_id, parent) in self.instances_ready.drain(..) {
if let Some(parent) = parent {
// Defer via commands otherwise SceneSpawner is not available in the observer.
world
.commands()
.trigger_targets(SceneInstanceReady { instance_id }, parent);
} else {
// Defer via commands otherwise SceneSpawner is not available in the observer.
world.commands().trigger(SceneInstanceReady { instance_id });
}
}
}
/// Check that a scene instance spawned previously is ready to use
pub fn instance_is_ready(&self, instance_id: InstanceId) -> bool {
self.spawned_instances.contains_key(&instance_id)
}
/// Get an iterator over the entities in an instance, once it's spawned.
///
/// Before the scene is spawned, the iterator will be empty. Use [`Self::instance_is_ready`]
/// to check if the instance is ready.
pub fn iter_instance_entities(
&'_ self,
instance_id: InstanceId,
) -> impl Iterator<Item = Entity> + '_ {
self.spawned_instances
.get(&instance_id)
.map(|instance| instance.entity_map.values())
.into_iter()
.flatten()
.copied()
}
}
/// System that handles scheduled scene instance spawning and despawning through a [`SceneSpawner`].
pub fn scene_spawner_system(world: &mut World) {
world.resource_scope(|world, mut scene_spawner: Mut<SceneSpawner>| {
// remove any loading instances where parent is deleted
let mut dead_instances = <HashSet<_>>::default();
scene_spawner
.scenes_with_parent
.retain(|(instance, parent)| {
let retain = world.get_entity(*parent).is_ok();
if !retain {
dead_instances.insert(*instance);
}
retain
});
scene_spawner
.dynamic_scenes_to_spawn
.retain(|(_, instance, _)| !dead_instances.contains(instance));
scene_spawner
.scenes_to_spawn
.retain(|(_, instance, _)| !dead_instances.contains(instance));
let scene_asset_events = world.resource::<Events<AssetEvent<DynamicScene>>>();
let mut updated_spawned_scenes = Vec::new();
let scene_spawner = &mut *scene_spawner;
for event in scene_spawner
.scene_asset_event_reader
.read(scene_asset_events)
{
if let AssetEvent::Modified { id } = event {
if scene_spawner.spawned_dynamic_scenes.contains_key(id) {
updated_spawned_scenes.push(*id);
}
}
}
scene_spawner.despawn_queued_scenes(world).unwrap();
scene_spawner.despawn_queued_instances(world);
scene_spawner
.spawn_queued_scenes(world)
.unwrap_or_else(|err| panic!("{}", err));
scene_spawner
.update_spawned_scenes(world, &updated_spawned_scenes)
.unwrap();
scene_spawner.set_scene_instance_parent_sync(world);
scene_spawner.trigger_scene_ready_events(world);
});
}
/// [`InstanceId`] of a spawned scene. It can be used with the [`SceneSpawner`] to
/// interact with the spawned scene.
#[derive(Component, Deref, DerefMut)]
pub struct SceneInstance(pub(crate) InstanceId);
/// System that will spawn scenes from the [`SceneRoot`] and [`DynamicSceneRoot`] components.
pub fn scene_spawner(
mut commands: Commands,
mut scene_to_spawn: Query<
(Entity, &SceneRoot, Option<&mut SceneInstance>),
(Changed<SceneRoot>, Without<DynamicSceneRoot>),
>,
mut dynamic_scene_to_spawn: Query<
(Entity, &DynamicSceneRoot, Option<&mut SceneInstance>),
(Changed<DynamicSceneRoot>, Without<SceneRoot>),
>,
mut scene_spawner: ResMut<SceneSpawner>,
) {
for (entity, scene, instance) in &mut scene_to_spawn {
let new_instance = scene_spawner.spawn_as_child(scene.0.clone(), entity);
if let Some(mut old_instance) = instance {
scene_spawner.despawn_instance(**old_instance);
*old_instance = SceneInstance(new_instance);
} else {
commands.entity(entity).insert(SceneInstance(new_instance));
}
}
for (entity, dynamic_scene, instance) in &mut dynamic_scene_to_spawn {
let new_instance = scene_spawner.spawn_dynamic_as_child(dynamic_scene.0.clone(), entity);
if let Some(mut old_instance) = instance {
scene_spawner.despawn_instance(**old_instance);
*old_instance = SceneInstance(new_instance);
} else {
commands.entity(entity).insert(SceneInstance(new_instance));
}
}
}
#[cfg(test)]
mod tests {
use bevy_app::App;
use bevy_asset::{AssetPlugin, AssetServer, Handle};
use bevy_ecs::{
component::Component,
hierarchy::Children,
observer::Trigger,
prelude::ReflectComponent,
query::With,
system::{Commands, Query, Res, ResMut, RunSystemOnce},
};
use bevy_reflect::Reflect;
use crate::{DynamicSceneBuilder, DynamicSceneRoot, ScenePlugin};
use super::*;
use crate::{DynamicScene, SceneSpawner};
use bevy_app::ScheduleRunnerPlugin;
use bevy_asset::Assets;
use bevy_ecs::{
entity::Entity,
prelude::{AppTypeRegistry, World},
};
#[derive(Component, Reflect, Default)]
#[reflect(Component)]
struct ComponentA {
pub x: f32,
pub y: f32,
}
#[test]
fn spawn_and_delete() {
let mut app = App::new();
app.add_plugins(ScheduleRunnerPlugin::default())
.add_plugins(AssetPlugin::default())
.add_plugins(ScenePlugin)
.register_type::<ComponentA>();
app.update();
let mut scene_world = World::new();
// create a new DynamicScene manually
let type_registry = app.world().resource::<AppTypeRegistry>().clone();
scene_world.insert_resource(type_registry);
scene_world.spawn(ComponentA { x: 3.0, y: 4.0 });
let scene = DynamicScene::from_world(&scene_world);
let scene_handle = app
.world_mut()
.resource_mut::<Assets<DynamicScene>>()
.add(scene);
// spawn the scene as a child of `entity` using `DynamicSceneRoot`
let entity = app
.world_mut()
.spawn(DynamicSceneRoot(scene_handle.clone()))
.id();
// run the app's schedule once, so that the scene gets spawned
app.update();
// make sure that the scene was added as a child of the root entity
let (scene_entity, scene_component_a) = app
.world_mut()
.query::<(Entity, &ComponentA)>()
.single(app.world())
.unwrap();
assert_eq!(scene_component_a.x, 3.0);
assert_eq!(scene_component_a.y, 4.0);
assert_eq!(
app.world().entity(entity).get::<Children>().unwrap().len(),
1
);
// let's try to delete the scene
let mut scene_spawner = app.world_mut().resource_mut::<SceneSpawner>();
scene_spawner.despawn(&scene_handle);
// run the scene spawner system to despawn the scene
app.update();
// the scene entity does not exist anymore
assert!(app.world().get_entity(scene_entity).is_err());
// the root entity does not have any children anymore
assert!(app.world().entity(entity).get::<Children>().is_none());
}
#[derive(Reflect, Component, Debug, PartialEq, Eq, Clone, Copy, Default)]
#[reflect(Component)]
struct A(usize);
#[test]
fn clone_dynamic_entities() {
let mut world = World::default();
// setup
let atr = AppTypeRegistry::default();
atr.write().register::<A>();
world.insert_resource(atr);
world.insert_resource(Assets::<DynamicScene>::default());
// start test
world.spawn(A(42));
assert_eq!(world.query::<&A>().iter(&world).len(), 1);
// clone only existing entity
let mut scene_spawner = SceneSpawner::default();
let entity = world
.query_filtered::<Entity, With<A>>()
.single(&world)
.unwrap();
let scene = DynamicSceneBuilder::from_world(&world)
.extract_entity(entity)
.build();
let scene_id = world.resource_mut::<Assets<DynamicScene>>().add(scene);
let instance_id = scene_spawner
.spawn_dynamic_sync(&mut world, &scene_id)
.unwrap();
// verify we spawned exactly one new entity with our expected component
assert_eq!(world.query::<&A>().iter(&world).len(), 2);
// verify that we can get this newly-spawned entity by the instance ID
let new_entity = scene_spawner
.iter_instance_entities(instance_id)
.next()
.unwrap();
// verify this is not the original entity
assert_ne!(entity, new_entity);
// verify this new entity contains the same data as the original entity
let [old_a, new_a] = world
.query::<&A>()
.get_many(&world, [entity, new_entity])
.unwrap();
assert_eq!(old_a, new_a);
}
#[derive(Component, Reflect, Default)]
#[reflect(Component)]
struct ComponentF;
#[derive(Resource, Default)]
struct TriggerCount(u32);
fn setup() -> App {
let mut app = App::new();
app.add_plugins((AssetPlugin::default(), ScenePlugin));
app.init_resource::<TriggerCount>();
app.register_type::<ComponentF>();
app.world_mut().spawn(ComponentF);
app.world_mut().spawn(ComponentF);
app
}
fn build_scene(app: &mut App) -> Handle<Scene> {
app.world_mut()
.run_system_once(
|world: &World,
type_registry: Res<'_, AppTypeRegistry>,
asset_server: Res<'_, AssetServer>| {
asset_server.add(
Scene::from_dynamic_scene(&DynamicScene::from_world(world), &type_registry)
.unwrap(),
)
},
)
.expect("Failed to run scene builder system.")
}
fn build_dynamic_scene(app: &mut App) -> Handle<DynamicScene> {
app.world_mut()
.run_system_once(|world: &World, asset_server: Res<'_, AssetServer>| {
asset_server.add(DynamicScene::from_world(world))
})
.expect("Failed to run dynamic scene builder system.")
}
fn observe_trigger(app: &mut App, scene_id: InstanceId, scene_entity: Entity) {
// Add observer
app.world_mut().add_observer(
move |trigger: Trigger<SceneInstanceReady>,
scene_spawner: Res<SceneSpawner>,
mut trigger_count: ResMut<TriggerCount>| {
assert_eq!(
trigger.event().instance_id,
scene_id,
"`SceneInstanceReady` contains the wrong `InstanceId`"
);
assert_eq!(
trigger.target(),
scene_entity,
"`SceneInstanceReady` triggered on the wrong parent entity"
);
assert!(
scene_spawner.instance_is_ready(trigger.event().instance_id),
"`InstanceId` is not ready"
);
trigger_count.0 += 1;
},
);
// Check observer is triggered once.
app.update();
app.world_mut()
.run_system_once(|trigger_count: Res<TriggerCount>| {
assert_eq!(
trigger_count.0, 1,
"wrong number of `SceneInstanceReady` triggers"
);
})
.unwrap();
}
#[test]
fn observe_scene() {
let mut app = setup();
// Build scene.
let scene = build_scene(&mut app);
// Spawn scene.
let scene_id = app
.world_mut()
.run_system_once(move |mut scene_spawner: ResMut<'_, SceneSpawner>| {
scene_spawner.spawn(scene.clone())
})
.unwrap();
// Check trigger.
observe_trigger(&mut app, scene_id, Entity::PLACEHOLDER);
}
#[test]
fn observe_dynamic_scene() {
let mut app = setup();
// Build scene.
let scene = build_dynamic_scene(&mut app);
// Spawn scene.
let scene_id = app
.world_mut()
.run_system_once(move |mut scene_spawner: ResMut<'_, SceneSpawner>| {
scene_spawner.spawn_dynamic(scene.clone())
})
.unwrap();
// Check trigger.
observe_trigger(&mut app, scene_id, Entity::PLACEHOLDER);
}
#[test]
fn observe_scene_as_child() {
let mut app = setup();
// Build scene.
let scene = build_scene(&mut app);
// Spawn scene as child.
let (scene_id, scene_entity) = app
.world_mut()
.run_system_once(
move |mut commands: Commands<'_, '_>,
mut scene_spawner: ResMut<'_, SceneSpawner>| {
let entity = commands.spawn_empty().id();
let id = scene_spawner.spawn_as_child(scene.clone(), entity);
(id, entity)
},
)
.unwrap();
// Check trigger.
observe_trigger(&mut app, scene_id, scene_entity);
}
#[test]
fn observe_dynamic_scene_as_child() {
let mut app = setup();
// Build scene.
let scene = build_dynamic_scene(&mut app);
// Spawn scene as child.
let (scene_id, scene_entity) = app
.world_mut()
.run_system_once(
move |mut commands: Commands<'_, '_>,
mut scene_spawner: ResMut<'_, SceneSpawner>| {
let entity = commands.spawn_empty().id();
let id = scene_spawner.spawn_dynamic_as_child(scene.clone(), entity);
(id, entity)
},
)
.unwrap();
// Check trigger.
observe_trigger(&mut app, scene_id, scene_entity);
}
#[test]
fn despawn_scene() {
let mut app = App::new();
app.add_plugins((AssetPlugin::default(), ScenePlugin));
app.register_type::<ComponentF>();
let asset_server = app.world().resource::<AssetServer>();
// Build scene.
let scene = asset_server.add(DynamicScene::default());
let count = 10;
// Checks the number of scene instances stored in `SceneSpawner`.
let check = |world: &mut World, expected_count: usize| {
let scene_spawner = world.resource::<SceneSpawner>();
assert_eq!(
scene_spawner.spawned_dynamic_scenes[&scene.id()].len(),
expected_count
);
assert_eq!(scene_spawner.spawned_instances.len(), expected_count);
};
// Spawn scene.
for _ in 0..count {
app.world_mut()
.spawn((ComponentF, DynamicSceneRoot(scene.clone())));
}
app.update();
check(app.world_mut(), count);
// Despawn scene.
app.world_mut()
.run_system_once(
|mut commands: Commands, query: Query<Entity, With<ComponentF>>| {
for entity in query.iter() {
commands.entity(entity).despawn();
}
},
)
.unwrap();
app.update();
check(app.world_mut(), 0);
}
#[test]
fn scene_child_order_preserved_when_archetype_order_mismatched() {
let mut app = App::new();
app.add_plugins(ScheduleRunnerPlugin::default())
.add_plugins(AssetPlugin::default())
.add_plugins(ScenePlugin)
.register_type::<ComponentA>()
.register_type::<ComponentF>();
app.update();
let mut scene_world = World::new();
let root = scene_world.spawn_empty().id();
let temporary_root = scene_world.spawn_empty().id();
// Spawn entities with different parent first before parenting them to the actual root, allowing us
// to decouple child order from archetype-creation-order
let child1 = scene_world
.spawn((ChildOf(temporary_root), ComponentA { x: 1.0, y: 1.0 }))
.id();
let child2 = scene_world
.spawn((ChildOf(temporary_root), ComponentA { x: 2.0, y: 2.0 }))
.id();
// the "first" child is intentionally spawned with a different component to force it into a "newer" archetype,
// meaning it will be iterated later in the spawn code.
let child0 = scene_world
.spawn((ChildOf(temporary_root), ComponentF))
.id();
scene_world
.entity_mut(root)
.add_children(&[child0, child1, child2]);
let scene = Scene::new(scene_world);
let scene_handle = app.world_mut().resource_mut::<Assets<Scene>>().add(scene);
let spawned = app.world_mut().spawn(SceneRoot(scene_handle.clone())).id();
app.update();
let world = app.world_mut();
let spawned_root = world.entity(spawned).get::<Children>().unwrap()[0];
let children = world.entity(spawned_root).get::<Children>().unwrap();
assert_eq!(children.len(), 3);
assert!(world.entity(children[0]).get::<ComponentF>().is_some());
assert_eq!(
world.entity(children[1]).get::<ComponentA>().unwrap().x,
1.0
);
assert_eq!(
world.entity(children[2]).get::<ComponentA>().unwrap().x,
2.0
);
}
}

1035
vendor/bevy_scene/src/serde.rs vendored Normal file

File diff suppressed because it is too large Load Diff