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

Reflect and (de)serialize resources in scenes #3580

Closed
wants to merge 11 commits into from
115 changes: 59 additions & 56 deletions assets/scenes/load_scene_example.scn.ron
Original file line number Diff line number Diff line change
@@ -1,64 +1,67 @@
[
(
entity: 0,
components: [
{
"type": "bevy_transform::components::transform::Transform",
"struct": {
"translation": {
"type": "glam::vec3::Vec3",
"value": (0.0, 0.0, 0.0),
},
"rotation": {
"type": "glam::quat::Quat",
"value": (0.0, 0.0, 0.0, 1.0),
},
"scale": {
"type": "glam::vec3::Vec3",
"value": (1.0, 1.0, 1.0),
(
resources: [],
entities: [
(
entity: 0,
components: [
{
"type": "bevy_transform::components::transform::Transform",
"struct": {
"translation": {
"type": "glam::vec3::Vec3",
"value": (0.0, 0.0, 0.0),
},
"rotation": {
"type": "glam::quat::Quat",
"value": (0.0, 0.0, 0.0, 1.0),
},
"scale": {
"type": "glam::vec3::Vec3",
"value": (1.0, 1.0, 1.0),
},
},
},
},
{
"type": "scene::ComponentB",
"struct": {
"value": {
"type": "alloc::string::String",
"value": "hello",
{
"type": "scene::ComponentB",
"struct": {
"value": {
"type": "alloc::string::String",
"value": "hello",
},
},
},
},
{
"type": "scene::ComponentA",
"struct": {
"x": {
"type": "f32",
"value": 1.0,
},
"y": {
"type": "f32",
"value": 2.0,
{
"type": "scene::ComponentA",
"struct": {
"x": {
"type": "f32",
"value": 1.0,
},
"y": {
"type": "f32",
"value": 2.0,
},
},
},
},
],
),
(
entity: 1,
components: [
{
"type": "scene::ComponentA",
"struct": {
"x": {
"type": "f32",
"value": 3.0,
},
"y": {
"type": "f32",
"value": 4.0,
],
),
(
entity: 1,
components: [
{
"type": "scene::ComponentA",
"struct": {
"x": {
"type": "f32",
"value": 3.0,
},
"y": {
"type": "f32",
"value": 4.0,
},
},
},
},
],
),
]
],
),
],
)
2 changes: 1 addition & 1 deletion crates/bevy_ecs/src/change_detection.rs
Original file line number Diff line number Diff line change
Expand Up @@ -188,7 +188,7 @@ change_detection_impl!(Mut<'a, T>, T,);
impl_into_inner!(Mut<'a, T>, T,);
impl_debug!(Mut<'a, T>,);

/// Unique mutable borrow of a Reflected component
/// Unique mutable borrow of a reflected component or resource
#[cfg(feature = "bevy_reflect")]
pub struct ReflectMut<'a> {
pub(crate) value: &'a mut dyn Reflect,
Expand Down
2 changes: 1 addition & 1 deletion crates/bevy_ecs/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ pub mod world;
pub mod prelude {
#[doc(hidden)]
#[cfg(feature = "bevy_reflect")]
pub use crate::reflect::ReflectComponent;
pub use crate::reflect::{ReflectComponent, ReflectResource};
#[doc(hidden)]
pub use crate::{
bundle::Bundle,
Expand Down
77 changes: 77 additions & 0 deletions crates/bevy_ecs/src/reflect.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ pub use crate::change_detection::ReflectMut;
use crate::{
component::Component,
entity::{Entity, EntityMap, MapEntities, MapEntitiesError},
system::Resource,
world::{FromWorld, World},
};
use bevy_reflect::{
Expand Down Expand Up @@ -122,6 +123,82 @@ impl<C: Component + Reflect + FromWorld> FromType<C> for ReflectComponent {
}
}

#[derive(Clone)]
pub struct ReflectResource {
insert_resource: fn(&mut World, &dyn Reflect),
apply_resource: fn(&mut World, &dyn Reflect),
remove_resource: fn(&mut World),
reflect_resource: fn(&World) -> Option<&dyn Reflect>,
reflect_resource_mut: unsafe fn(&World) -> Option<ReflectMut>,
copy_resource: fn(&World, &mut World),
}

impl ReflectResource {
pub fn insert_resource(&self, world: &mut World, resource: &dyn Reflect) {
(self.insert_resource)(world, resource);
}

pub fn apply_resource(&self, world: &mut World, resource: &dyn Reflect) {
(self.apply_resource)(world, resource);
}

pub fn remove_resource(&self, world: &mut World) {
(self.remove_resource)(world);
}

pub fn reflect_resource<'a>(&self, world: &'a World) -> Option<&'a dyn Reflect> {
(self.reflect_resource)(world)
}

/// # Safety
/// This method does not prevent you from having two mutable pointers to the same data,
/// violating Rust's aliasing rules. To avoid this:
/// * Only call this method in an exclusive system to avoid sharing across threads (or use a
/// scheduler that enforces safe memory access).
/// * Don't call this method more than once in the same scope for a given resource.
pub unsafe fn reflect_resource_mut<'a>(&self, world: &'a World) -> Option<ReflectMut<'a>> {
Davier marked this conversation as resolved.
Show resolved Hide resolved
(self.reflect_resource_mut)(world)
}

pub fn copy_resource(&self, source_world: &World, destination_world: &mut World) {
(self.copy_resource)(source_world, destination_world);
}
}

impl<C: Resource + Reflect + FromWorld> FromType<C> for ReflectResource {
fn from_type() -> Self {
ReflectResource {
insert_resource: |world, reflected_resource| {
let mut resource = C::from_world(world);
resource.apply(reflected_resource);
world.insert_resource(resource);
},
apply_resource: |world, reflected_resource| {
let mut resource = world.get_resource_mut::<C>().unwrap();
resource.apply(reflected_resource);
},
remove_resource: |world| {
world.remove_resource::<C>();
},
reflect_resource: |world| world.get_resource::<C>().map(|res| res as &dyn Reflect),
reflect_resource_mut: |world| unsafe {
world
.get_resource_unchecked_mut::<C>()
.map(|res| ReflectMut {
value: res.value as &mut dyn Reflect,
ticks: res.ticks,
})
},
copy_resource: |source_world, destination_world| {
let source_resource = source_world.get_resource::<C>().unwrap();
let mut destination_resource = C::from_world(destination_world);
destination_resource.apply(source_resource);
destination_world.insert_resource(destination_resource);
},
}
}
}

impl_reflect_value!(Entity(Hash, PartialEq, Serialize, Deserialize));
impl_from_reflect_value!(Entity);

Expand Down
19 changes: 12 additions & 7 deletions crates/bevy_ecs/src/world/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -644,16 +644,21 @@ impl World {
Some(unsafe { ptr.cast::<T>().read() })
}

/// Returns `true` if a resource of a given [`TypeId`] exists. Otherwise returns `false`.
#[inline]
pub fn contains_resource_type_id(&self, type_id: TypeId) -> bool {
Davier marked this conversation as resolved.
Show resolved Hide resolved
let component_id = if let Some(component_id) = self.components.get_resource_id(type_id) {
component_id
} else {
return false;
};
self.get_populated_resource_column(component_id).is_some()
}

/// Returns `true` if a resource of type `T` exists. Otherwise returns `false`.
#[inline]
pub fn contains_resource<T: Resource>(&self) -> bool {
let component_id =
if let Some(component_id) = self.components.get_resource_id(TypeId::of::<T>()) {
component_id
} else {
return false;
};
self.get_populated_resource_column(component_id).is_some()
self.contains_resource_type_id(TypeId::of::<T>())
}

/// Gets a reference to the resource of the given type, if it exists. Otherwise returns [None]
Expand Down
51 changes: 47 additions & 4 deletions crates/bevy_scene/src/dynamic_scene.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ use crate::{serde::SceneSerializer, Scene, SceneSpawnError};
use anyhow::Result;
use bevy_ecs::{
entity::EntityMap,
reflect::{ReflectComponent, ReflectMapEntities},
reflect::{ReflectComponent, ReflectMapEntities, ReflectResource},
world::World,
};
use bevy_reflect::{Reflect, TypeRegistryArc, TypeUuid};
Expand All @@ -12,6 +12,7 @@ use serde::Serialize;
#[derive(Default, TypeUuid)]
#[uuid = "749479b1-fb8c-4ff8-a775-623aa76014f5"]
pub struct DynamicScene {
pub resources: Vec<Box<dyn Reflect>>,
pub entities: Vec<DynamicEntity>,
}

Expand All @@ -35,7 +36,28 @@ impl DynamicScene {
let mut scene = DynamicScene::default();
let type_registry = type_registry.read();

for archetype in world.archetypes().iter() {
let mut archetypes = world.archetypes().iter();

// Empty archetype
let _ = archetypes.next().unwrap();

// Resources archetype
let resources_archetype = archetypes.next().unwrap();
Davier marked this conversation as resolved.
Show resolved Hide resolved
for component_id in resources_archetype.components() {
let reflect_resource = world
.components()
.get_info(component_id)
.and_then(|info| type_registry.get(info.type_id().unwrap()))
Davier marked this conversation as resolved.
Show resolved Hide resolved
.and_then(|registration| registration.data::<ReflectResource>());
if let Some(reflect_resource) = reflect_resource {
if let Some(resource) = reflect_resource.reflect_resource(world) {
scene.resources.push(resource.clone_value());
}
}
}

// Other archetypes
for archetype in archetypes {
Davier 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
Expand Down Expand Up @@ -78,9 +100,30 @@ impl DynamicScene {
&self,
world: &mut World,
entity_map: &mut EntityMap,
type_registry: &TypeRegistryArc,
) -> Result<(), SceneSpawnError> {
let registry = world.get_resource::<TypeRegistryArc>().unwrap().clone();
let type_registry = registry.read();
let type_registry = type_registry.read();

for resource in self.resources.iter() {
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 entity already has the given resource attached,
Davier marked this conversation as resolved.
Show resolved Hide resolved
// just apply the (possibly) new value, otherwise insert the resource
if world.contains_resource_type_id(registration.type_id()) {
reflect_resource.apply_resource(world, &**resource);
} else {
reflect_resource.insert_resource(world, &**resource);
}
}

for scene_entity in self.entities.iter() {
// Fetch the entity with the given entity id from the `entity_map`
Expand Down
Loading