Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

(De) serialize resources in scenes #6846

Merged
merged 14 commits into from
Mar 20, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions assets/scenes/load_scene_example.scn.ron
Original file line number Diff line number Diff line change
@@ -1,4 +1,9 @@
(
resources: {
"scene::ResourceA": (
score: 2,
),
},
entities: {
0: (
components: {
Expand Down
30 changes: 26 additions & 4 deletions crates/bevy_scene/src/dynamic_scene.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,13 @@ use bevy_reflect::{Reflect, TypeRegistryArc, TypeUuid};

#[cfg(feature = "serialize")]
use crate::serde::SceneSerializer;
use bevy_ecs::reflect::ReflectResource;
#[cfg(feature = "serialize")]
use serde::Serialize;

/// A collection of serializable dynamic entities, each with its own run-time defined set of components.
/// 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 [`DynamicSceneBundle`](crate::DynamicSceneBundle) to an entity
Expand All @@ -23,6 +26,7 @@ use serde::Serialize;
#[derive(Default, TypeUuid)]
#[uuid = "749479b1-fb8c-4ff8-a775-623aa76014f5"]
pub struct DynamicScene {
pub resources: Vec<Box<dyn Reflect>>,
Carbonhell marked this conversation as resolved.
Show resolved Hide resolved
pub entities: Vec<DynamicEntity>,
}

Expand All @@ -47,15 +51,16 @@ impl DynamicScene {
DynamicSceneBuilder::from_world_with_type_registry(world, type_registry.clone());

builder.extract_entities(world.iter_entities().map(|entity| entity.id()));
builder.extract_resources();

builder.build()
}

/// Write the dynamic entities and their corresponding components to the given world.
/// 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) trait.
/// [`Component`](bevy_ecs::component::Component) or [`Resource`](bevy_ecs::prelude::Resource) trait.
pub fn write_to_world_with(
&self,
world: &mut World,
Expand All @@ -64,6 +69,23 @@ impl DynamicScene {
) -> Result<(), SceneSpawnError> {
let type_registry = type_registry.read();

for resource in &self.resources {
let registration = type_registry
.get_with_name(resource.type_name())
.ok_or_else(|| SceneSpawnError::UnregisteredType {
type_name: resource.type_name().to_string(),
})?;
let reflect_resource = registration.data::<ReflectResource>().ok_or_else(|| {
SceneSpawnError::UnregisteredResource {
type_name: resource.type_name().to_string(),
}
})?;

// 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, &**resource);
}

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
Expand Down Expand Up @@ -105,7 +127,7 @@ impl DynamicScene {
Ok(())
}

/// Write the dynamic entities and their corresponding components to the given world.
/// 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
Expand Down
129 changes: 115 additions & 14 deletions crates/bevy_scene/src/dynamic_scene_builder.rs
Original file line number Diff line number Diff line change
@@ -1,10 +1,16 @@
use crate::{DynamicEntity, DynamicScene};
use bevy_app::AppTypeRegistry;
use bevy_ecs::{prelude::Entity, reflect::ReflectComponent, world::World};
use bevy_ecs::component::ComponentId;
use bevy_ecs::{
prelude::Entity,
reflect::{ReflectComponent, ReflectResource},
world::World,
};
use bevy_reflect::Reflect;
use bevy_utils::default;
use std::collections::BTreeMap;

/// A [`DynamicScene`] builder, used to build a scene from a [`World`] by extracting some entities.
/// A [`DynamicScene`] builder, used to build a scene from a [`World`] by extracting some entities and resources.
///
/// # Entity Order
///
Expand All @@ -31,6 +37,7 @@ use std::collections::BTreeMap;
/// let dynamic_scene = builder.build();
/// ```
pub struct DynamicSceneBuilder<'w> {
extracted_resources: BTreeMap<ComponentId, Box<dyn Reflect>>,
extracted_scene: BTreeMap<u32, DynamicEntity>,
type_registry: AppTypeRegistry,
original_world: &'w World,
Expand All @@ -41,6 +48,7 @@ impl<'w> DynamicSceneBuilder<'w> {
/// All components registered in that world's [`AppTypeRegistry`] resource will be extracted.
pub fn from_world(world: &'w World) -> Self {
Self {
extracted_resources: default(),
extracted_scene: default(),
type_registry: world.resource::<AppTypeRegistry>().clone(),
original_world: world,
Expand All @@ -51,6 +59,7 @@ impl<'w> DynamicSceneBuilder<'w> {
/// Only components registered in the given [`AppTypeRegistry`] will be extracted.
pub fn from_world_with_type_registry(world: &'w World, type_registry: AppTypeRegistry) -> Self {
Self {
extracted_resources: default(),
extracted_scene: default(),
type_registry,
original_world: world,
Expand All @@ -63,6 +72,7 @@ impl<'w> DynamicSceneBuilder<'w> {
/// [`Self::remove_empty_entities`] before building the scene.
pub fn build(self) -> DynamicScene {
DynamicScene {
resources: self.extracted_resources.into_values().collect(),
entities: self.extracted_scene.into_values().collect(),
}
}
Expand Down Expand Up @@ -126,31 +136,80 @@ impl<'w> DynamicSceneBuilder<'w> {

let entity = self.original_world.entity(entity);
for component_id in entity.archetype().components() {
let reflect_component = self
.original_world
.components()
.get_info(component_id)
.and_then(|info| type_registry.get(info.type_id().unwrap()))
.and_then(|registration| registration.data::<ReflectComponent>())
.and_then(|reflect_component| reflect_component.reflect(entity));

if let Some(reflect_component) = reflect_component {
entry.components.push(reflect_component.clone_value());
}
let mut extract_and_push = || {
let type_id = self
.original_world
.components()
.get_info(component_id)?
.type_id()?;
let component = type_registry
.get(type_id)?
.data::<ReflectComponent>()?
.reflect(entity)?;
entry.components.push(component.clone_value());
Some(())
};
extract_and_push();
}
self.extracted_scene.insert(index, entry);
}

drop(type_registry);
self
}

/// Extract resources from the builder's [`World`].
///
/// Only resources registered in the builder's [`AppTypeRegistry`] will be extracted.
/// Re-extracting a resource that was already extracted will have no effect.
/// ```
/// # use bevy_scene::DynamicSceneBuilder;
/// # use bevy_app::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);
/// builder.extract_resources();
/// let scene = builder.build();
/// ```
pub fn extract_resources(&mut self) -> &mut Self {
let type_registry = self.type_registry.read();
for (component_id, _) in self.original_world.storages().resources.iter() {
let mut extract_and_push = || {
let type_id = self
.original_world
.components()
.get_info(component_id)?
.type_id()?;
let resource = type_registry
.get(type_id)?
.data::<ReflectResource>()?
.reflect(self.original_world)?;
self.extracted_resources
.insert(component_id, resource.clone_value());
Some(())
};
extract_and_push();
}

drop(type_registry);
self
}
}

#[cfg(test)]
mod tests {
use bevy_app::AppTypeRegistry;
use bevy_ecs::{
component::Component, prelude::Entity, query::With, reflect::ReflectComponent, world::World,
component::Component, prelude::Entity, prelude::Resource, query::With,
reflect::ReflectComponent, reflect::ReflectResource, world::World,
};

use bevy_reflect::Reflect;
Expand All @@ -160,10 +219,15 @@ mod tests {
#[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;

#[test]
fn extract_one_entity() {
let mut world = World::default();
Expand Down Expand Up @@ -303,4 +367,41 @@ mod tests {
assert_eq!(scene.entities.len(), 1);
assert_eq!(scene.entities[0].entity, entity_a.index());
}

#[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 mut builder = DynamicSceneBuilder::from_world(&world);
builder.extract_resources();
let scene = builder.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 mut builder = DynamicSceneBuilder::from_world(&world);
builder.extract_resources();
builder.extract_resources();
let scene = builder.build();

assert_eq!(scene.resources.len(), 1);
assert!(scene.resources[0].represents::<ResourceA>());
}
}
29 changes: 28 additions & 1 deletion crates/bevy_scene/src/scene.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
use bevy_app::AppTypeRegistry;
use bevy_ecs::{
entity::EntityMap,
reflect::{ReflectComponent, ReflectMapEntities},
reflect::{ReflectComponent, ReflectMapEntities, ReflectResource},
world::World,
};
use bevy_reflect::TypeUuid;
Expand Down Expand Up @@ -61,6 +61,33 @@ impl Scene {
};

let type_registry = type_registry.read();

// Resources archetype
for (component_id, _) in self.world.storages().resources.iter() {
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 {
type_name: component_info.name().to_string(),
})?;
let reflect_resource = registration.data::<ReflectResource>().ok_or_else(|| {
SceneSpawnError::UnregisteredResource {
type_name: component_info.name().to_string(),
}
})?;
reflect_resource.copy(&self.world, world);
}

for archetype in self.world.archetypes().iter() {
for scene_entity in archetype.entities() {
let entity = *instance_info
Expand Down
2 changes: 2 additions & 0 deletions crates/bevy_scene/src/scene_spawner.rs
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,8 @@ pub struct SceneSpawner {
pub enum SceneSpawnError {
#[error("scene contains the unregistered component `{type_name}`. consider adding `#[reflect(Component)]` to your type")]
UnregisteredComponent { type_name: String },
#[error("scene contains the unregistered resource `{type_name}`. consider adding `#[reflect(Resource)]` to your type")]
UnregisteredResource { type_name: String },
#[error("scene contains the unregistered type `{type_name}`. consider registering the type using `app.register_type::<T>()`")]
UnregisteredType { type_name: String },
#[error("scene does not exist")]
Expand Down
Loading