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

Create a DynamicScene from a world and an entity root #6013

Closed
wants to merge 7 commits into from
Closed
Show file tree
Hide file tree
Changes from 3 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
74 changes: 69 additions & 5 deletions crates/bevy_scene/src/dynamic_scene.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,11 @@ use anyhow::Result;
use bevy_app::AppTypeRegistry;
use bevy_ecs::{
entity::EntityMap,
prelude::Entity,
reflect::{ReflectComponent, ReflectMapEntities},
world::World,
};
use bevy_hierarchy::Children;
use bevy_reflect::{Reflect, TypeRegistryArc, TypeUuid};
use serde::Serialize;

Expand Down Expand Up @@ -33,12 +35,12 @@ pub struct DynamicEntity {

impl DynamicScene {
/// Create a new dynamic scene from a given scene.
pub fn from_scene(scene: &Scene, type_registry: &TypeRegistryArc) -> Self {
pub fn from_scene(scene: &Scene, type_registry: &AppTypeRegistry) -> Self {
Self::from_world(&scene.world, type_registry)
}

/// Create a new dynamic scene from a given world.
pub fn from_world(world: &World, type_registry: &TypeRegistryArc) -> Self {
pub fn from_world(world: &World, type_registry: &AppTypeRegistry) -> Self {
let mut scene = DynamicScene::default();
let type_registry = type_registry.read();

Expand Down Expand Up @@ -76,17 +78,79 @@ impl DynamicScene {
scene
}

/// Create a new dynamic scene from a given world at a given root.
mockersf marked this conversation as resolved.
Show resolved Hide resolved
pub fn from_world_at_root(
Copy link
Contributor

Choose a reason for hiding this comment

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

Bikeshed: i think the "at root" naming is confusing. I'd probably have called this from_world_entity_recursive.

Copy link
Member Author

Choose a reason for hiding this comment

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

totally open to changing the name, even more if we change it to take several entities as input

Copy link
Member

Choose a reason for hiding this comment

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

IMO from_entity_recursive or from_entities_and_children or something. I think the from_world bit is unhelpful in context and can just be dropped.

Copy link
Member

Choose a reason for hiding this comment

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

I actually cast my vote for a simple from_root. Imo it's implicit that we're talking about worlds and entities here. If we want to be a little more specific, I think from_entity is fine too— although, it could be slightly confusing as to whether this recurses into children or not.

Next best would probably be from_entity_recursive.

world: &World,
root: Entity,
Copy link
Contributor

Choose a reason for hiding this comment

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

Personally, I think this method would have limited practical utility. Who wants to create a scene from just one entity (+ children)? Sure, sometimes you might have such a use case, but it seems needlessly limited. What if you want a second entity? Or a few more?

Why not change this to impl IntoIterator<Item=Entity>, allowing users to provide any number of root entities?

Would add a little extra complexity to the body of the function. Perhaps there should be some sort of deduplication logic to prevent redundant/duplicate entities in the scene. One suggestion might be to use an intermediary HashSet instead of a Vec. Or maybe we could just not do any deduplication (just accumulate all the entities into the scene without regard for duplicates), and document it as a caveat / leave it to the users to make sure they don't have duplicate entities in their list of roots.

It would make this function much more practical and useful.

Copy link
Member Author

Choose a reason for hiding this comment

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

Would you prefer to be able to append to an existing scene? Or just creating from multiple entities

Copy link
Contributor

Choose a reason for hiding this comment

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

Well now that you have said it ... i guess both? 😆

Copy link
Member

Choose a reason for hiding this comment

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

I imagine single-entity scenes could be super useful for prefab-like serialization and usage.

But I agree it could also be useful to handle multiple at a time. Perhaps a method for a single root and another for multiple?

type_registry: &AppTypeRegistry,
) -> Self {
let mut scene = DynamicScene::default();
let type_registry = type_registry.read();

// Extract all entities that are a descendent of root
let mut entities_of_interest = Vec::new();
let mut entities_to_process = vec![root];
while let Some(entity) = entities_to_process.pop() {
mockersf marked this conversation as resolved.
Show resolved Hide resolved
entities_of_interest.push(entity);
if let Some(children) = world.entity(entity).get::<Children>() {
entities_to_process.extend(children.iter());
}
}

for archetype in world.archetypes().iter() {
mockersf marked this conversation as resolved.
Show resolved Hide resolved
let entities_offset = scene.entities.len();

// Create a new dynamic entity for each entity of the given archetype
// and insert it into the dynamic scene.
for entity in archetype
.entities()
.iter()
.filter(|entity| entities_of_interest.contains(entity))
mockersf marked this conversation as resolved.
Show resolved Hide resolved
{
scene.entities.push(DynamicEntity {
mockersf marked this conversation as resolved.
Show resolved Hide resolved
entity: entity.id(),
components: Vec::new(),
});
}

// Add each reflection-powered component to the entity it belongs to.
for component_id in archetype.components() {
let reflect_component = world
.components()
.get_info(component_id)
.and_then(|info| type_registry.get(info.type_id().unwrap()))
.and_then(|registration| registration.data::<ReflectComponent>());
if let Some(reflect_component) = reflect_component {
for (i, entity) in archetype
.entities()
.iter()
.filter(|entity| entities_of_interest.contains(entity))
.enumerate()
{
if let Some(component) = reflect_component.reflect(world, *entity) {
scene.entities[entities_offset + i]
.components
.push(component.clone_value());
}
}
}
}
}

scene
}

/// Write the dynamic entities and their corresponding components to the given world.
///
/// This method will return a [`SceneSpawnError`] if a type either is not registered
/// or doesn't reflect the [`Component`](bevy_ecs::component::Component) trait.
Comment on lines 124 to 127
Copy link
Member

Choose a reason for hiding this comment

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

Documentation should include information about the registry filter.

pub fn write_to_world(
pub fn write_to_world_with(
Copy link
Member

Choose a reason for hiding this comment

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

I think this should be a separate method so users that want to extract everything can don't need to add the extra world.resource::<AppTypeRegistry>() call.

&self,
world: &mut World,
entity_map: &mut EntityMap,
type_registry: &AppTypeRegistry,
) -> Result<(), SceneSpawnError> {
let registry = world.resource::<AppTypeRegistry>().clone();
let type_registry = registry.read();
let type_registry = type_registry.read();

for scene_entity in &self.entities {
// Fetch the entity with the given entity id from the `entity_map`
Expand Down
17 changes: 15 additions & 2 deletions crates/bevy_scene/src/scene.rs
Original file line number Diff line number Diff line change
@@ -1,20 +1,21 @@
use bevy_app::AppTypeRegistry;
use bevy_derive::{Deref, DerefMut};
use bevy_ecs::{
entity::EntityMap,
reflect::{ReflectComponent, ReflectMapEntities},
world::World,
};
use bevy_reflect::TypeUuid;

use crate::{InstanceInfo, SceneSpawnError};
use crate::{DynamicScene, InstanceInfo, SceneSpawnError};

/// To spawn a scene, you can use either:
/// * [`SceneSpawner::spawn`](crate::SceneSpawner::spawn)
/// * adding the [`SceneBundle`](crate::SceneBundle) to an entity
/// * adding the [`Handle<Scene>`](bevy_asset::Handle) to an entity (the scene will only be
/// visible if the entity already has [`Transform`](bevy_transform::components::Transform) and
/// [`GlobalTransform`](bevy_transform::components::GlobalTransform) components)
#[derive(Debug, TypeUuid)]
#[derive(Debug, TypeUuid, Deref, DerefMut)]
Copy link
Member

Choose a reason for hiding this comment

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

I'm reluctant to add these derives; they make things more implicit and are likely to break once we need to add more fields.

Copy link
Member

Choose a reason for hiding this comment

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

IMO the Deref / DerefMut impls on Scene should be their own PR so they get appropriate review attention.

Copy link
Member Author

Choose a reason for hiding this comment

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

'm reluctant to add these derives; they make things more implicit and are likely to break once we need to add more fields.

Then let's remove them later if we need to add more fields? For now they make working with scenes a lot nicer.

Copy link
Contributor

Choose a reason for hiding this comment

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

Disagreed. Why introduce extra implicitness and potential for future breaking changes, just to save on typing a .0. I wouldn't call this "a lot nicer".

Copy link
Member Author

@mockersf mockersf Sep 18, 2022

Choose a reason for hiding this comment

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

Well then we're back to arguing against the Deref derive that we promotes in Bevy: https://bevyengine.org/news/bevy-0-7/#deref-derefmut-derives

I'm not sure there are plans to add more fields to Scene at the moment?

Copy link
Member

Choose a reason for hiding this comment

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

Hmm, I'm fine to leave it. We should write a manual Deref / DerefMut impl if and when we add more fields then, and call this out in the docs though.

Copy link
Member

Choose a reason for hiding this comment

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

Personally, I don't think the Deref/DerefMut derives are needed. It's probably better for users to explicitly be aware that what they're working with is basically just a plain ol' World. And yeah, it makes future additions to Scenes less problematic (e.g. versioning, etc.).

#[uuid = "c156503c-edd9-4ec7-8d33-dab392df03cd"]
pub struct Scene {
pub world: World,
Expand All @@ -25,6 +26,18 @@ impl Scene {
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 = EntityMap::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
Expand Down
6 changes: 5 additions & 1 deletion crates/bevy_scene/src/scene_spawner.rs
Original file line number Diff line number Diff line change
Expand Up @@ -144,7 +144,11 @@ impl SceneSpawner {
.ok_or_else(|| SceneSpawnError::NonExistentScene {
handle: scene_handle.clone_weak(),
})?;
scene.write_to_world(world, entity_map)
scene.write_to_world_with(
world,
entity_map,
&world.resource::<AppTypeRegistry>().clone(),
mockersf marked this conversation as resolved.
Show resolved Hide resolved
)
})
}

Expand Down