diff --git a/.github/example-run/breakout.ron b/.github/example-run/breakout.ron index 1d78f6a73a..f2036f4a49 100644 --- a/.github/example-run/breakout.ron +++ b/.github/example-run/breakout.ron @@ -1,3 +1,5 @@ ( - exit_after: Some(900) + exit_after: Some(900), + frame_time: Some(0.03), + screenshot_frames: [200], ) diff --git a/.github/example-run/load_gltf.ron b/.github/example-run/load_gltf.ron index d170958d73..13f79f298c 100644 --- a/.github/example-run/load_gltf.ron +++ b/.github/example-run/load_gltf.ron @@ -1,3 +1,5 @@ ( - exit_after: Some(300) + exit_after: Some(300), + frame_time: Some(0.03), + screenshot_frames: [100], ) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index f4b890ff8b..1db57174ae 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -199,13 +199,23 @@ jobs: echo "running $example_name - "`date` time TRACE_CHROME=trace-$example_name.json CI_TESTING_CONFIG=$example xvfb-run cargo run --example $example_name --features "bevy_ci_testing,trace,trace_chrome" sleep 10 + if [ `find ./ -maxdepth 1 -name 'screenshot-*.png' -print -quit` ]; then + mkdir screenshots-$example_name + mv screenshot-*.png screenshots-$example_name/ + fi done zip traces.zip trace*.json + zip -r screenshots.zip screenshots-* - name: save traces uses: actions/upload-artifact@v3 with: name: example-traces.zip path: traces.zip + - name: save screenshots + uses: actions/upload-artifact@v3 + with: + name: screenshots.zip + path: screenshots.zip - name: Save PR number if: ${{ failure() && github.event_name == 'pull_request' }} run: | diff --git a/Cargo.toml b/Cargo.toml index 5309ec5eb3..c30d603882 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -229,6 +229,12 @@ glam_assert = ["bevy_internal/glam_assert"] # Include a default font, containing only ASCII characters, at the cost of a 20kB binary size increase default_font = ["bevy_internal/default_font"] +# Enable support for shaders in GLSL +shader_format_glsl = ["bevy_internal/shader_format_glsl"] + +# Enable support for shaders in SPIR-V +shader_format_spirv = ["bevy_internal/shader_format_spirv"] + [dependencies] bevy_dylib = { path = "crates/bevy_dylib", version = "0.11.0-dev", default-features = false, optional = true } bevy_internal = { path = "crates/bevy_internal", version = "0.11.0-dev", default-features = false } @@ -1972,7 +1978,7 @@ path = "examples/window/screenshot.rs" name = "Screenshot" description = "Shows how to save screenshots to disk" category = "Window" -wasm = false +wasm = true [[example]] name = "transparent_window" diff --git a/benches/benches/bevy_reflect/struct.rs b/benches/benches/bevy_reflect/struct.rs index 782d5e0b73..0495701807 100644 --- a/benches/benches/bevy_reflect/struct.rs +++ b/benches/benches/bevy_reflect/struct.rs @@ -148,14 +148,14 @@ fn concrete_struct_type_info(criterion: &mut Criterion) { BenchmarkId::new("NonGeneric", field_count), &standard, |bencher, s| { - bencher.iter(|| black_box(s.get_type_info())); + bencher.iter(|| black_box(s.get_represented_type_info())); }, ); group.bench_with_input( BenchmarkId::new("Generic", field_count), &generic, |bencher, s| { - bencher.iter(|| black_box(s.get_type_info())); + bencher.iter(|| black_box(s.get_represented_type_info())); }, ); } diff --git a/crates/bevy_app/src/app.rs b/crates/bevy_app/src/app.rs index 404ed09a49..780b1ada97 100644 --- a/crates/bevy_app/src/app.rs +++ b/crates/bevy_app/src/app.rs @@ -236,9 +236,11 @@ impl App { /// /// The active schedule of the app must be set before this method is called. pub fn update(&mut self) { + #[cfg(feature = "trace")] + let _bevy_update_span = info_span!("update").entered(); { #[cfg(feature = "trace")] - let _bevy_frame_update_span = info_span!("main app").entered(); + let _bevy_main_update_span = info_span!("main app").entered(); self.world.run_schedule(&*self.main_schedule_label); } for (_label, sub_app) in self.sub_apps.iter_mut() { diff --git a/crates/bevy_app/src/ci_testing.rs b/crates/bevy_app/src/ci_testing.rs index f662a350a4..3ba0dde78e 100644 --- a/crates/bevy_app/src/ci_testing.rs +++ b/crates/bevy_app/src/ci_testing.rs @@ -1,3 +1,5 @@ +//! Utilities for testing in CI environments. + use crate::{app::AppExit, App, Update}; use serde::Deserialize; @@ -13,6 +15,11 @@ use bevy_utils::tracing::info; pub struct CiTestingConfig { /// The number of frames after which Bevy should exit. pub exit_after: Option, + /// The time in seconds to update for each frame. + pub frame_time: Option, + /// Frames at which to capture a screenshot. + #[serde(default)] + pub screenshot_frames: Vec, } fn ci_testing_exit_after( diff --git a/crates/bevy_app/src/lib.rs b/crates/bevy_app/src/lib.rs index ee409da3d1..f9ecd22569 100644 --- a/crates/bevy_app/src/lib.rs +++ b/crates/bevy_app/src/lib.rs @@ -10,7 +10,7 @@ mod plugin_group; mod schedule_runner; #[cfg(feature = "bevy_ci_testing")] -mod ci_testing; +pub mod ci_testing; pub use app::*; pub use bevy_derive::DynamicPlugin; diff --git a/crates/bevy_asset/src/loader.rs b/crates/bevy_asset/src/loader.rs index 2b76edadf8..55611b6a18 100644 --- a/crates/bevy_asset/src/loader.rs +++ b/crates/bevy_asset/src/loader.rs @@ -1,6 +1,6 @@ use crate::{ path::AssetPath, AssetIo, AssetIoError, AssetMeta, AssetServer, Assets, Handle, HandleId, - RefChangeChannel, + HandleUntyped, RefChangeChannel, }; use anyhow::Error; use anyhow::Result; @@ -166,11 +166,16 @@ impl<'a> LoadContext<'a> { self.get_handle(AssetPath::new_ref(self.path(), Some(label))) } - /// Gets a handle to an asset of type `T` from its id. + /// Gets a strong handle to an asset of type `T` from its id. pub fn get_handle, T: Asset>(&self, id: I) -> Handle { Handle::strong(id.into(), self.ref_change_channel.sender.clone()) } + /// Gets an untyped strong handle for an asset with the provided id. + pub fn get_handle_untyped>(&self, id: I) -> HandleUntyped { + HandleUntyped::strong(id.into(), self.ref_change_channel.sender.clone()) + } + /// Reads the contents of the file at the specified path through the [`AssetIo`] associated /// with this context. pub async fn read_asset_bytes>(&self, path: P) -> Result, AssetIoError> { diff --git a/crates/bevy_core/src/name.rs b/crates/bevy_core/src/name.rs index 429feb9fb4..32216cb280 100644 --- a/crates/bevy_core/src/name.rs +++ b/crates/bevy_core/src/name.rs @@ -17,8 +17,8 @@ use std::{ /// [`Name`] should not be treated as a globally unique identifier for entities, /// as multiple entities can have the same name. [`bevy_ecs::entity::Entity`] should be /// used instead as the default unique identifier. -#[derive(Reflect, FromReflect, Component, Debug, Clone)] -#[reflect(Component, Default)] +#[derive(Reflect, FromReflect, Component, Clone)] +#[reflect(Component, Default, Debug)] pub struct Name { hash: u64, // TODO: Shouldn't be serialized name: Cow<'static, str>, @@ -79,6 +79,13 @@ impl std::fmt::Display for Name { } } +impl std::fmt::Debug for Name { + #[inline(always)] + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + std::fmt::Debug::fmt(&self.name, f) + } +} + /// Convenient query for giving a human friendly name to an entity. /// /// ```rust diff --git a/crates/bevy_core_pipeline/src/bloom/mod.rs b/crates/bevy_core_pipeline/src/bloom/mod.rs index 3f042ff5e8..474ed90533 100644 --- a/crates/bevy_core_pipeline/src/bloom/mod.rs +++ b/crates/bevy_core_pipeline/src/bloom/mod.rs @@ -30,7 +30,6 @@ use downsampling_pipeline::{ prepare_downsampling_pipeline, BloomDownsamplingPipeline, BloomDownsamplingPipelineIds, BloomUniforms, }; -use std::num::NonZeroU32; use upsampling_pipeline::{ prepare_upsampling_pipeline, BloomUpsamplingPipeline, UpsamplingPipelineIds, }; @@ -312,7 +311,7 @@ impl BloomTexture { fn view(&self, base_mip_level: u32) -> TextureView { self.texture.texture.create_view(&TextureViewDescriptor { base_mip_level, - mip_level_count: NonZeroU32::new(1), + mip_level_count: Some(1u32), ..Default::default() }) } diff --git a/crates/bevy_ecs/Cargo.toml b/crates/bevy_ecs/Cargo.toml index cfcea53b36..c19073b2c9 100644 --- a/crates/bevy_ecs/Cargo.toml +++ b/crates/bevy_ecs/Cargo.toml @@ -26,7 +26,7 @@ thread_local = "1.1.4" fixedbitset = "0.4.2" rustc-hash = "1.1" downcast-rs = "1.2" -serde = { version = "1", features = ["derive"] } +serde = "1" thiserror = "1.0" [dev-dependencies] diff --git a/crates/bevy_ecs/src/entity/map_entities.rs b/crates/bevy_ecs/src/entity/map_entities.rs index 2a8dbfbc46..739635f532 100644 --- a/crates/bevy_ecs/src/entity/map_entities.rs +++ b/crates/bevy_ecs/src/entity/map_entities.rs @@ -1,24 +1,5 @@ -use crate::entity::Entity; +use crate::{entity::Entity, world::World}; use bevy_utils::{Entry, HashMap}; -use std::fmt; - -/// The errors that might be returned while using [`MapEntities::map_entities`]. -#[derive(Debug)] -pub enum MapEntitiesError { - EntityNotFound(Entity), -} - -impl std::error::Error for MapEntitiesError {} - -impl fmt::Display for MapEntitiesError { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - match self { - MapEntitiesError::EntityNotFound(_) => { - write!(f, "the given entity does not exist in the map") - } - } - } -} /// Operation to map all contained [`Entity`] fields in a type to new values. /// @@ -33,7 +14,7 @@ impl fmt::Display for MapEntitiesError { /// /// ```rust /// use bevy_ecs::prelude::*; -/// use bevy_ecs::entity::{EntityMap, MapEntities, MapEntitiesError}; +/// use bevy_ecs::entity::{EntityMapper, MapEntities}; /// /// #[derive(Component)] /// struct Spring { @@ -42,10 +23,9 @@ impl fmt::Display for MapEntitiesError { /// } /// /// impl MapEntities for Spring { -/// fn map_entities(&mut self, entity_map: &EntityMap) -> Result<(), MapEntitiesError> { -/// self.a = entity_map.get(self.a)?; -/// self.b = entity_map.get(self.b)?; -/// Ok(()) +/// fn map_entities(&mut self, entity_mapper: &mut EntityMapper) { +/// self.a = entity_mapper.get_or_reserve(self.a); +/// self.b = entity_mapper.get_or_reserve(self.b); /// } /// } /// ``` @@ -55,16 +35,22 @@ pub trait MapEntities { /// Updates all [`Entity`] references stored inside using `entity_map`. /// /// Implementors should look up any and all [`Entity`] values stored within and - /// update them to the mapped values via `entity_map`. - fn map_entities(&mut self, entity_map: &EntityMap) -> Result<(), MapEntitiesError>; + /// update them to the mapped values via `entity_mapper`. + fn map_entities(&mut self, entity_mapper: &mut EntityMapper); } /// A mapping from one set of entities to another. /// /// The API generally follows [`HashMap`], but each [`Entity`] is returned by value, as they are [`Copy`]. /// -/// This is typically used to coordinate data transfer between sets of entities, such as between a scene and the world or over the network. -/// This is required as [`Entity`] identifiers are opaque; you cannot and do not want to reuse identifiers directly. +/// This is typically used to coordinate data transfer between sets of entities, such as between a scene and the world +/// or over the network. This is required as [`Entity`] identifiers are opaque; you cannot and do not want to reuse +/// identifiers directly. +/// +/// On its own, an `EntityMap` is not capable of allocating new entity identifiers, which is needed to map references +/// to entities that lie outside the source entity set. To do this, an `EntityMap` can be wrapped in an +/// [`EntityMapper`] which scopes it to a particular destination [`World`] and allows new identifiers to be allocated. +/// This functionality can be accessed through [`Self::world_scope()`]. #[derive(Default, Debug)] pub struct EntityMap { map: HashMap, @@ -91,11 +77,8 @@ impl EntityMap { } /// Returns the corresponding mapped entity. - pub fn get(&self, entity: Entity) -> Result { - self.map - .get(&entity) - .cloned() - .ok_or(MapEntitiesError::EntityNotFound(entity)) + pub fn get(&self, entity: Entity) -> Option { + self.map.get(&entity).copied() } /// An iterator visiting all keys in arbitrary order. @@ -122,4 +105,138 @@ impl EntityMap { pub fn iter(&self) -> impl Iterator + '_ { self.map.iter().map(|(from, to)| (*from, *to)) } + + /// Creates an [`EntityMapper`] from this [`EntityMap`] and scoped to the provided [`World`], then calls the + /// provided function with it. This allows one to allocate new entity references in the provided `World` that are + /// guaranteed to never point at a living entity now or in the future. This functionality is useful for safely + /// mapping entity identifiers that point at entities outside the source world. The passed function, `f`, is called + /// within the scope of the passed world. Its return value is then returned from `world_scope` as the generic type + /// parameter `R`. + pub fn world_scope( + &mut self, + world: &mut World, + f: impl FnOnce(&mut World, &mut EntityMapper) -> R, + ) -> R { + let mut mapper = EntityMapper::new(self, world); + let result = f(world, &mut mapper); + mapper.finish(world); + result + } +} + +/// A wrapper for [`EntityMap`], augmenting it with the ability to allocate new [`Entity`] references in a destination +/// world. These newly allocated references are guaranteed to never point to any living entity in that world. +/// +/// References are allocated by returning increasing generations starting from an internally initialized base +/// [`Entity`]. After it is finished being used by [`MapEntities`] implementations, this entity is despawned and the +/// requisite number of generations reserved. +pub struct EntityMapper<'m> { + /// The wrapped [`EntityMap`]. + map: &'m mut EntityMap, + /// A base [`Entity`] used to allocate new references. + dead_start: Entity, + /// The number of generations this mapper has allocated thus far. + generations: u32, +} + +impl<'m> EntityMapper<'m> { + /// Returns the corresponding mapped entity or reserves a new dead entity ID if it is absent. + pub fn get_or_reserve(&mut self, entity: Entity) -> Entity { + if let Some(mapped) = self.map.get(entity) { + return mapped; + } + + // this new entity reference is specifically designed to never represent any living entity + let new = Entity { + generation: self.dead_start.generation + self.generations, + index: self.dead_start.index, + }; + self.generations += 1; + + self.map.insert(entity, new); + + new + } + + /// Gets a reference to the underlying [`EntityMap`]. + pub fn get_map(&'m self) -> &'m EntityMap { + self.map + } + + /// Gets a mutable reference to the underlying [`EntityMap`] + pub fn get_map_mut(&'m mut self) -> &'m mut EntityMap { + self.map + } + + /// Creates a new [`EntityMapper`], spawning a temporary base [`Entity`] in the provided [`World`] + fn new(map: &'m mut EntityMap, world: &mut World) -> Self { + Self { + map, + // SAFETY: Entities data is kept in a valid state via `EntityMap::world_scope` + dead_start: unsafe { world.entities_mut().alloc() }, + generations: 0, + } + } + + /// Reserves the allocated references to dead entities within the world. This frees the temporary base + /// [`Entity`] while reserving extra generations via [`crate::entity::Entities::reserve_generations`]. Because this + /// renders the [`EntityMapper`] unable to safely allocate any more references, this method takes ownership of + /// `self` in order to render it unusable. + fn finish(self, world: &mut World) { + // SAFETY: Entities data is kept in a valid state via `EntityMap::world_scope` + let entities = unsafe { world.entities_mut() }; + assert!(entities.free(self.dead_start).is_some()); + assert!(entities.reserve_generations(self.dead_start.index, self.generations)); + } +} + +#[cfg(test)] +mod tests { + use super::{EntityMap, EntityMapper}; + use crate::{entity::Entity, world::World}; + + #[test] + fn entity_mapper() { + const FIRST_IDX: u32 = 1; + const SECOND_IDX: u32 = 2; + + let mut map = EntityMap::default(); + let mut world = World::new(); + let mut mapper = EntityMapper::new(&mut map, &mut world); + + let mapped_ent = Entity::new(FIRST_IDX, 0); + let dead_ref = mapper.get_or_reserve(mapped_ent); + + assert_eq!( + dead_ref, + mapper.get_or_reserve(mapped_ent), + "should persist the allocated mapping from the previous line" + ); + assert_eq!( + mapper.get_or_reserve(Entity::new(SECOND_IDX, 0)).index(), + dead_ref.index(), + "should re-use the same index for further dead refs" + ); + + mapper.finish(&mut world); + // Next allocated entity should be a further generation on the same index + let entity = world.spawn_empty().id(); + assert_eq!(entity.index(), dead_ref.index()); + assert!(entity.generation() > dead_ref.generation()); + } + + #[test] + fn world_scope_reserves_generations() { + let mut map = EntityMap::default(); + let mut world = World::new(); + + let dead_ref = map.world_scope(&mut world, |_, mapper| { + mapper.get_or_reserve(Entity::new(0, 0)) + }); + + // Next allocated entity should be a further generation on the same index + let entity = world.spawn_empty().id(); + assert_eq!(entity.index(), dead_ref.index()); + assert!(entity.generation() > dead_ref.generation()); + } } diff --git a/crates/bevy_ecs/src/entity/mod.rs b/crates/bevy_ecs/src/entity/mod.rs index 0dcbe44581..38fecdbd51 100644 --- a/crates/bevy_ecs/src/entity/mod.rs +++ b/crates/bevy_ecs/src/entity/mod.rs @@ -115,7 +115,7 @@ type IdCursor = isize; /// [`EntityCommands`]: crate::system::EntityCommands /// [`Query::get`]: crate::system::Query::get /// [`World`]: crate::world::World -#[derive(Clone, Copy, Deserialize, Eq, Hash, Ord, PartialEq, PartialOrd, Serialize)] +#[derive(Clone, Copy, Eq, Hash, Ord, PartialEq, PartialOrd)] pub struct Entity { generation: u32, index: u32, @@ -227,6 +227,25 @@ impl Entity { } } +impl Serialize for Entity { + fn serialize(&self, serializer: S) -> Result + where + S: serde::Serializer, + { + serializer.serialize_u64(self.to_bits()) + } +} + +impl<'de> Deserialize<'de> for Entity { + fn deserialize(deserializer: D) -> Result + where + D: serde::Deserializer<'de>, + { + let id: u64 = serde::de::Deserialize::deserialize(deserializer)?; + Ok(Entity::from_bits(id)) + } +} + impl fmt::Debug for Entity { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "{}v{}", self.index, self.generation) @@ -604,6 +623,25 @@ impl Entities { self.meta.get_unchecked_mut(index as usize).location = location; } + /// Increments the `generation` of a freed [`Entity`]. The next entity ID allocated with this + /// `index` will count `generation` starting from the prior `generation` + the specified + /// value + 1. + /// + /// Does nothing if no entity with this `index` has been allocated yet. + pub(crate) fn reserve_generations(&mut self, index: u32, generations: u32) -> bool { + if (index as usize) >= self.meta.len() { + return false; + } + + let meta = &mut self.meta[index as usize]; + if meta.location.archetype_id == ArchetypeId::INVALID { + meta.generation += generations; + true + } else { + false + } + } + /// Get the [`Entity`] with a given id, if it exists in this [`Entities`] collection /// Returns `None` if this [`Entity`] is outside of the range of currently reserved Entities /// @@ -847,4 +885,29 @@ mod tests { const C4: u32 = Entity::from_bits(0x00dd_00ff_0000_0000).generation(); assert_eq!(0x00dd_00ff, C4); } + + #[test] + fn reserve_generations() { + let mut entities = Entities::new(); + let entity = entities.alloc(); + entities.free(entity); + + assert!(entities.reserve_generations(entity.index, 1)); + } + + #[test] + fn reserve_generations_and_alloc() { + const GENERATIONS: u32 = 10; + + let mut entities = Entities::new(); + let entity = entities.alloc(); + entities.free(entity); + + assert!(entities.reserve_generations(entity.index, GENERATIONS)); + + // The very next entitiy allocated should be a further generation on the same index + let next_entity = entities.alloc(); + assert_eq!(next_entity.index(), entity.index()); + assert!(next_entity.generation > entity.generation + GENERATIONS); + } } diff --git a/crates/bevy_ecs/src/reflect.rs b/crates/bevy_ecs/src/reflect.rs index 91781e74fc..62be9c9ae4 100644 --- a/crates/bevy_ecs/src/reflect.rs +++ b/crates/bevy_ecs/src/reflect.rs @@ -3,7 +3,7 @@ use crate::{ change_detection::Mut, component::Component, - entity::{Entity, EntityMap, MapEntities, MapEntitiesError}, + entity::{Entity, EntityMap, EntityMapper, MapEntities}, system::Resource, world::{ unsafe_world_cell::{UnsafeEntityCell, UnsafeWorldCell}, @@ -412,8 +412,8 @@ impl_from_reflect_value!(Entity); #[derive(Clone)] pub struct ReflectMapEntities { - map_entities: fn(&mut World, &EntityMap) -> Result<(), MapEntitiesError>, - map_specific_entities: fn(&mut World, &EntityMap, &[Entity]) -> Result<(), MapEntitiesError>, + map_all_entities: fn(&mut World, &mut EntityMapper), + map_entities: fn(&mut World, &mut EntityMapper, &[Entity]), } impl ReflectMapEntities { @@ -424,49 +424,42 @@ impl ReflectMapEntities { /// by the [`EntityMap`] might already contain valid entity references, you should use [`map_entities`](Self::map_entities). /// /// An example of this: A scene can be loaded with `Parent` components, but then a `Parent` component can be added - /// to these entities after they have been loaded. If you reload the scene using [`map_entities`](Self::map_entities), those `Parent` + /// to these entities after they have been loaded. If you reload the scene using [`map_all_entities`](Self::map_all_entities), those `Parent` /// components with already valid entity references could be updated to point at something else entirely. - pub fn map_entities( - &self, - world: &mut World, - entity_map: &EntityMap, - ) -> Result<(), MapEntitiesError> { - (self.map_entities)(world, entity_map) + pub fn map_all_entities(&self, world: &mut World, entity_map: &mut EntityMap) { + entity_map.world_scope(world, self.map_all_entities); } - /// This is like [`map_entities`](Self::map_entities), but only applied to specific entities, not all values + /// A general method for applying [`MapEntities`] behavior to elements in an [`EntityMap`]. Unlike + /// [`map_all_entities`](Self::map_all_entities), this is applied to specific entities, not all values /// in the [`EntityMap`]. /// /// This is useful mostly for when you need to be careful not to update components that already contain valid entity - /// values. See [`map_entities`](Self::map_entities) for more details. - pub fn map_specific_entities( - &self, - world: &mut World, - entity_map: &EntityMap, - entities: &[Entity], - ) -> Result<(), MapEntitiesError> { - (self.map_specific_entities)(world, entity_map, entities) + /// values. See [`map_all_entities`](Self::map_all_entities) for more details. + pub fn map_entities(&self, world: &mut World, entity_map: &mut EntityMap, entities: &[Entity]) { + entity_map.world_scope(world, |world, mapper| { + (self.map_entities)(world, mapper, entities); + }); } } impl FromType for ReflectMapEntities { fn from_type() -> Self { ReflectMapEntities { - map_specific_entities: |world, entity_map, entities| { + map_entities: |world, entity_mapper, entities| { for &entity in entities { if let Some(mut component) = world.get_mut::(entity) { - component.map_entities(entity_map)?; + component.map_entities(entity_mapper); } } - Ok(()) }, - map_entities: |world, entity_map| { - for entity in entity_map.values() { - if let Some(mut component) = world.get_mut::(entity) { - component.map_entities(entity_map)?; + map_all_entities: |world, entity_mapper| { + let entities = entity_mapper.get_map().values().collect::>(); + for entity in &entities { + if let Some(mut component) = world.get_mut::(*entity) { + component.map_entities(entity_mapper); } } - Ok(()) }, } } diff --git a/crates/bevy_ecs/src/system/commands/mod.rs b/crates/bevy_ecs/src/system/commands/mod.rs index 06271dc22c..e5db31f50b 100644 --- a/crates/bevy_ecs/src/system/commands/mod.rs +++ b/crates/bevy_ecs/src/system/commands/mod.rs @@ -62,6 +62,10 @@ pub trait Command: Send + 'static { /// * inserting resources /// * etc. /// +/// For a version of [`Commands`] that works in parallel contexts (such as +/// within [`Query::par_iter`](crate::system::Query::par_iter)) see +/// [`ParallelCommands`] +/// /// # Usage /// /// Add `mut commands: Commands` as a function argument to your system to get a copy of this struct that will be applied the next time a copy of [`apply_system_buffers`] runs. diff --git a/crates/bevy_hierarchy/src/components/children.rs b/crates/bevy_hierarchy/src/components/children.rs index 541b48d68a..02d9e0e388 100644 --- a/crates/bevy_hierarchy/src/components/children.rs +++ b/crates/bevy_hierarchy/src/components/children.rs @@ -1,6 +1,6 @@ use bevy_ecs::{ component::Component, - entity::{Entity, EntityMap, MapEntities, MapEntitiesError}, + entity::{Entity, EntityMapper, MapEntities}, prelude::FromWorld, reflect::{ReflectComponent, ReflectMapEntities}, world::World, @@ -21,12 +21,10 @@ use std::ops::Deref; pub struct Children(pub(crate) SmallVec<[Entity; 8]>); impl MapEntities for Children { - fn map_entities(&mut self, entity_map: &EntityMap) -> Result<(), MapEntitiesError> { + fn map_entities(&mut self, entity_mapper: &mut EntityMapper) { for entity in &mut self.0 { - *entity = entity_map.get(*entity)?; + *entity = entity_mapper.get_or_reserve(*entity); } - - Ok(()) } } @@ -50,6 +48,83 @@ impl Children { pub fn swap(&mut self, a_index: usize, b_index: usize) { self.0.swap(a_index, b_index); } + + /// Sorts children [stably](https://en.wikipedia.org/wiki/Sorting_algorithm#Stability) + /// in place using the provided comparator function. + /// + /// For the underlying implementation, see [`slice::sort_by`]. + /// + /// For the unstable version, see [`sort_unstable_by`](Children::sort_unstable_by). + /// + /// See also [`sort_by_key`](Children::sort_by_key), [`sort_by_cached_key`](Children::sort_by_cached_key). + pub fn sort_by(&mut self, compare: F) + where + F: FnMut(&Entity, &Entity) -> std::cmp::Ordering, + { + self.0.sort_by(compare); + } + + /// Sorts children [stably](https://en.wikipedia.org/wiki/Sorting_algorithm#Stability) + /// in place using the provided key extraction function. + /// + /// For the underlying implementation, see [`slice::sort_by_key`]. + /// + /// For the unstable version, see [`sort_unstable_by_key`](Children::sort_unstable_by_key). + /// + /// See also [`sort_by`](Children::sort_by), [`sort_by_cached_key`](Children::sort_by_cached_key). + pub fn sort_by_key(&mut self, compare: F) + where + F: FnMut(&Entity) -> K, + K: Ord, + { + self.0.sort_by_key(compare); + } + + /// Sorts children [stably](https://en.wikipedia.org/wiki/Sorting_algorithm#Stability) + /// in place using the provided key extraction function. Only evaluates each key at most + /// once per sort, caching the intermediate results in memory. + /// + /// For the underlying implementation, see [`slice::sort_by_cached_key`]. + /// + /// See also [`sort_by`](Children::sort_by), [`sort_by_key`](Children::sort_by_key). + pub fn sort_by_cached_key(&mut self, compare: F) + where + F: FnMut(&Entity) -> K, + K: Ord, + { + self.0.sort_by_cached_key(compare); + } + + /// Sorts children [unstably](https://en.wikipedia.org/wiki/Sorting_algorithm#Stability) + /// in place using the provided comparator function. + /// + /// For the underlying implementation, see [`slice::sort_unstable_by`]. + /// + /// For the stable version, see [`sort_by`](Children::sort_by). + /// + /// See also [`sort_unstable_by_key`](Children::sort_unstable_by_key). + pub fn sort_unstable_by(&mut self, compare: F) + where + F: FnMut(&Entity, &Entity) -> std::cmp::Ordering, + { + self.0.sort_unstable_by(compare); + } + + /// Sorts children [unstably](https://en.wikipedia.org/wiki/Sorting_algorithm#Stability) + /// in place using the provided key extraction function. + /// + /// For the underlying implementation, see [`slice::sort_unstable_by_key`]. + /// + /// For the stable version, see [`sort_by_key`](Children::sort_by_key). + /// + /// See also [`sort_unstable_by`](Children::sort_unstable_by). + pub fn sort_unstable_by_key(&mut self, compare: F) + where + F: FnMut(&Entity) -> K, + K: Ord, + { + self.0.sort_unstable_by_key(compare); + } } impl Deref for Children { diff --git a/crates/bevy_hierarchy/src/components/parent.rs b/crates/bevy_hierarchy/src/components/parent.rs index 3e573be5a6..7bccf33ed6 100644 --- a/crates/bevy_hierarchy/src/components/parent.rs +++ b/crates/bevy_hierarchy/src/components/parent.rs @@ -1,6 +1,6 @@ use bevy_ecs::{ component::Component, - entity::{Entity, EntityMap, MapEntities, MapEntitiesError}, + entity::{Entity, EntityMapper, MapEntities}, reflect::{ReflectComponent, ReflectMapEntities}, world::{FromWorld, World}, }; @@ -36,13 +36,8 @@ impl FromWorld for Parent { } impl MapEntities for Parent { - fn map_entities(&mut self, entity_map: &EntityMap) -> Result<(), MapEntitiesError> { - // Parent of an entity in the new world can be in outside world, in which case it - // should not be mapped. - if let Ok(mapped_entity) = entity_map.get(self.0) { - self.0 = mapped_entity; - } - Ok(()) + fn map_entities(&mut self, entity_mapper: &mut EntityMapper) { + self.0 = entity_mapper.get_or_reserve(self.0); } } diff --git a/crates/bevy_internal/Cargo.toml b/crates/bevy_internal/Cargo.toml index 51fe644c92..712ce1890a 100644 --- a/crates/bevy_internal/Cargo.toml +++ b/crates/bevy_internal/Cargo.toml @@ -57,6 +57,10 @@ symphonia-isomp4 = ["bevy_audio/symphonia-isomp4"] symphonia-vorbis = ["bevy_audio/symphonia-vorbis"] symphonia-wav = ["bevy_audio/symphonia-wav"] +# Shader formats +shader_format_glsl = ["bevy_render/shader_format_glsl"] +shader_format_spirv = ["bevy_render/shader_format_spirv"] + # Enable watching file system for asset hot reload filesystem_watcher = ["bevy_asset/filesystem_watcher"] @@ -73,7 +77,7 @@ subpixel_glyph_atlas = ["bevy_text/subpixel_glyph_atlas"] webgl = ["bevy_core_pipeline?/webgl", "bevy_pbr?/webgl", "bevy_render?/webgl"] # enable systems that allow for automated testing on CI -bevy_ci_testing = ["bevy_app/bevy_ci_testing", "bevy_render?/ci_limits"] +bevy_ci_testing = ["bevy_app/bevy_ci_testing", "bevy_time/bevy_ci_testing", "bevy_render?/bevy_ci_testing", "bevy_render?/ci_limits"] # Enable animation support, and glTF animation loading animation = ["bevy_animation", "bevy_gltf?/bevy_animation"] diff --git a/crates/bevy_pbr/src/prepass/mod.rs b/crates/bevy_pbr/src/prepass/mod.rs index 958c253cc8..cbebb80912 100644 --- a/crates/bevy_pbr/src/prepass/mod.rs +++ b/crates/bevy_pbr/src/prepass/mod.rs @@ -561,7 +561,7 @@ pub fn get_bind_group_layout_entries( visibility: ShaderStages::FRAGMENT, ty: BindingType::Texture { multisampled, - sample_type: TextureSampleType::Float { filterable: true }, + sample_type: TextureSampleType::Float { filterable: false }, view_dimension: TextureViewDimension::D2, }, count: None, @@ -572,7 +572,7 @@ pub fn get_bind_group_layout_entries( visibility: ShaderStages::FRAGMENT, ty: BindingType::Texture { multisampled, - sample_type: TextureSampleType::Float { filterable: true }, + sample_type: TextureSampleType::Float { filterable: false }, view_dimension: TextureViewDimension::D2, }, count: None, diff --git a/crates/bevy_pbr/src/render/light.rs b/crates/bevy_pbr/src/render/light.rs index dc28bdfc06..1e73198a87 100644 --- a/crates/bevy_pbr/src/render/light.rs +++ b/crates/bevy_pbr/src/render/light.rs @@ -30,10 +30,7 @@ use bevy_utils::{ tracing::{error, warn}, HashMap, }; -use std::{ - hash::Hash, - num::{NonZeroU32, NonZeroU64}, -}; +use std::{hash::Hash, num::NonZeroU64}; #[derive(Debug, Hash, PartialEq, Eq, Clone, SystemSet)] pub enum RenderLightSystems { @@ -998,7 +995,7 @@ pub fn prepare_lights( base_mip_level: 0, mip_level_count: None, base_array_layer: (light_index * 6 + face_index) as u32, - array_layer_count: NonZeroU32::new(1), + array_layer_count: Some(1u32), }); let view_light_entity = commands @@ -1060,7 +1057,7 @@ pub fn prepare_lights( base_mip_level: 0, mip_level_count: None, base_array_layer: (num_directional_cascades_enabled + light_index) as u32, - array_layer_count: NonZeroU32::new(1), + array_layer_count: Some(1u32), }); let view_light_entity = commands @@ -1124,7 +1121,7 @@ pub fn prepare_lights( base_mip_level: 0, mip_level_count: None, base_array_layer: directional_depth_texture_array_index, - array_layer_count: NonZeroU32::new(1), + array_layer_count: Some(1u32), }); directional_depth_texture_array_index += 1; diff --git a/crates/bevy_pbr/src/render/mesh.rs b/crates/bevy_pbr/src/render/mesh.rs index 34a95480a9..5ce6d410ef 100644 --- a/crates/bevy_pbr/src/render/mesh.rs +++ b/crates/bevy_pbr/src/render/mesh.rs @@ -548,12 +548,7 @@ impl FromWorld for MeshPipeline { &image.data, ImageDataLayout { offset: 0, - bytes_per_row: Some( - std::num::NonZeroU32::new( - image.texture_descriptor.size.width * format_size as u32, - ) - .unwrap(), - ), + bytes_per_row: Some(image.texture_descriptor.size.width * format_size as u32), rows_per_image: None, }, image.texture_descriptor.size, diff --git a/crates/bevy_reflect/bevy_reflect_derive/src/impls/enums.rs b/crates/bevy_reflect/bevy_reflect_derive/src/impls/enums.rs index 666283615d..2f2f5cf5e3 100644 --- a/crates/bevy_reflect/bevy_reflect_derive/src/impls/enums.rs +++ b/crates/bevy_reflect/bevy_reflect_derive/src/impls/enums.rs @@ -192,8 +192,8 @@ pub(crate) fn impl_enum(reflect_enum: &ReflectEnum) -> TokenStream { } #[inline] - fn get_type_info(&self) -> &'static #bevy_reflect_path::TypeInfo { - ::type_info() + fn get_represented_type_info(&self) -> #FQOption<&'static #bevy_reflect_path::TypeInfo> { + #FQOption::Some(::type_info()) } #[inline] diff --git a/crates/bevy_reflect/bevy_reflect_derive/src/impls/structs.rs b/crates/bevy_reflect/bevy_reflect_derive/src/impls/structs.rs index e22634e5de..2b7fd1815c 100644 --- a/crates/bevy_reflect/bevy_reflect_derive/src/impls/structs.rs +++ b/crates/bevy_reflect/bevy_reflect_derive/src/impls/structs.rs @@ -151,7 +151,7 @@ pub(crate) fn impl_struct(reflect_struct: &ReflectStruct) -> TokenStream { fn clone_dynamic(&self) -> #bevy_reflect_path::DynamicStruct { let mut dynamic: #bevy_reflect_path::DynamicStruct = #FQDefault::default(); - dynamic.set_name(::std::string::ToString::to_string(#bevy_reflect_path::Reflect::type_name(self))); + dynamic.set_represented_type(#bevy_reflect_path::Reflect::get_represented_type_info(self)); #(dynamic.insert_boxed(#field_names, #bevy_reflect_path::Reflect::clone_value(&self.#field_idents));)* dynamic } @@ -164,8 +164,8 @@ pub(crate) fn impl_struct(reflect_struct: &ReflectStruct) -> TokenStream { } #[inline] - fn get_type_info(&self) -> &'static #bevy_reflect_path::TypeInfo { - ::type_info() + fn get_represented_type_info(&self) -> #FQOption<&'static #bevy_reflect_path::TypeInfo> { + #FQOption::Some(::type_info()) } #[inline] diff --git a/crates/bevy_reflect/bevy_reflect_derive/src/impls/tuple_structs.rs b/crates/bevy_reflect/bevy_reflect_derive/src/impls/tuple_structs.rs index c41c53b86e..c74d4d9064 100644 --- a/crates/bevy_reflect/bevy_reflect_derive/src/impls/tuple_structs.rs +++ b/crates/bevy_reflect/bevy_reflect_derive/src/impls/tuple_structs.rs @@ -122,7 +122,7 @@ pub(crate) fn impl_tuple_struct(reflect_struct: &ReflectStruct) -> TokenStream { fn clone_dynamic(&self) -> #bevy_reflect_path::DynamicTupleStruct { let mut dynamic: #bevy_reflect_path::DynamicTupleStruct = #FQDefault::default(); - dynamic.set_name(::std::string::ToString::to_string(#bevy_reflect_path::Reflect::type_name(self))); + dynamic.set_represented_type(#bevy_reflect_path::Reflect::get_represented_type_info(self)); #(dynamic.insert_boxed(#bevy_reflect_path::Reflect::clone_value(&self.#field_idents));)* dynamic } @@ -135,8 +135,8 @@ pub(crate) fn impl_tuple_struct(reflect_struct: &ReflectStruct) -> TokenStream { } #[inline] - fn get_type_info(&self) -> &'static #bevy_reflect_path::TypeInfo { - ::type_info() + fn get_represented_type_info(&self) -> #FQOption<&'static #bevy_reflect_path::TypeInfo> { + #FQOption::Some(::type_info()) } #[inline] diff --git a/crates/bevy_reflect/bevy_reflect_derive/src/impls/values.rs b/crates/bevy_reflect/bevy_reflect_derive/src/impls/values.rs index 4aa3aed418..964ef1f2ce 100644 --- a/crates/bevy_reflect/bevy_reflect_derive/src/impls/values.rs +++ b/crates/bevy_reflect/bevy_reflect_derive/src/impls/values.rs @@ -49,8 +49,8 @@ pub(crate) fn impl_value(meta: &ReflectMeta) -> TokenStream { } #[inline] - fn get_type_info(&self) -> &'static #bevy_reflect_path::TypeInfo { - ::type_info() + fn get_represented_type_info(&self) -> #FQOption<&'static #bevy_reflect_path::TypeInfo> { + #FQOption::Some(::type_info()) } #[inline] diff --git a/crates/bevy_reflect/src/array.rs b/crates/bevy_reflect/src/array.rs index 947601a154..0367e1add4 100644 --- a/crates/bevy_reflect/src/array.rs +++ b/crates/bevy_reflect/src/array.rs @@ -1,7 +1,4 @@ -use crate::{ - utility::{reflect_hasher, NonGenericTypeInfoCell}, - DynamicInfo, Reflect, ReflectMut, ReflectOwned, ReflectRef, TypeInfo, Typed, -}; +use crate::{utility::reflect_hasher, Reflect, ReflectMut, ReflectOwned, ReflectRef, TypeInfo}; use std::{ any::{Any, TypeId}, fmt::Debug, @@ -67,7 +64,7 @@ pub trait Array: Reflect { /// Clones the list, producing a [`DynamicArray`]. fn clone_dynamic(&self) -> DynamicArray { DynamicArray { - name: self.type_name().to_string(), + represented_type: self.get_represented_type_info(), values: self.iter().map(|value| value.clone_value()).collect(), } } @@ -167,7 +164,7 @@ impl ArrayInfo { /// [`DynamicList`]: crate::DynamicList #[derive(Debug)] pub struct DynamicArray { - pub(crate) name: String, + pub(crate) represented_type: Option<&'static TypeInfo>, pub(crate) values: Box<[Box]>, } @@ -175,14 +172,14 @@ impl DynamicArray { #[inline] pub fn new(values: Box<[Box]>) -> Self { Self { - name: String::default(), + represented_type: None, values, } } pub fn from_vec(values: Vec) -> Self { Self { - name: String::default(), + represented_type: None, values: values .into_iter() .map(|field| Box::new(field) as Box) @@ -191,26 +188,37 @@ impl DynamicArray { } } - #[inline] - pub fn name(&self) -> &str { - &self.name - } + /// Sets the [type] to be represented by this `DynamicArray`. + /// + /// # Panics + /// + /// Panics if the given [type] is not a [`TypeInfo::Array`]. + /// + /// [type]: TypeInfo + pub fn set_represented_type(&mut self, represented_type: Option<&'static TypeInfo>) { + if let Some(represented_type) = represented_type { + assert!( + matches!(represented_type, TypeInfo::Array(_)), + "expected TypeInfo::Array but received: {:?}", + represented_type + ); + } - #[inline] - pub fn set_name(&mut self, name: String) { - self.name = name; + self.represented_type = represented_type; } } impl Reflect for DynamicArray { #[inline] fn type_name(&self) -> &str { - self.name.as_str() + self.represented_type + .map(|info| info.type_name()) + .unwrap_or_else(|| std::any::type_name::()) } #[inline] - fn get_type_info(&self) -> &'static TypeInfo { - ::type_info() + fn get_represented_type_info(&self) -> Option<&'static TypeInfo> { + self.represented_type } #[inline] @@ -281,6 +289,11 @@ impl Reflect for DynamicArray { fn reflect_partial_eq(&self, value: &dyn Reflect) -> Option { array_partial_eq(self, value) } + + #[inline] + fn is_dynamic(&self) -> bool { + true + } } impl Array for DynamicArray { @@ -312,7 +325,7 @@ impl Array for DynamicArray { #[inline] fn clone_dynamic(&self) -> DynamicArray { DynamicArray { - name: self.name.clone(), + represented_type: self.represented_type, values: self .values .iter() @@ -322,13 +335,6 @@ impl Array for DynamicArray { } } -impl Typed for DynamicArray { - fn type_info() -> &'static TypeInfo { - static CELL: NonGenericTypeInfoCell = NonGenericTypeInfoCell::new(); - CELL.get_or_set(|| TypeInfo::Dynamic(DynamicInfo::new::())) - } -} - /// An iterator over an [`Array`]. pub struct ArrayIter<'a> { array: &'a dyn Array, diff --git a/crates/bevy_reflect/src/enums/dynamic_enum.rs b/crates/bevy_reflect/src/enums/dynamic_enum.rs index db9e050fbe..1e4cce1278 100644 --- a/crates/bevy_reflect/src/enums/dynamic_enum.rs +++ b/crates/bevy_reflect/src/enums/dynamic_enum.rs @@ -1,8 +1,6 @@ -use crate::utility::NonGenericTypeInfoCell; use crate::{ - enum_debug, enum_hash, enum_partial_eq, DynamicInfo, DynamicStruct, DynamicTuple, Enum, - Reflect, ReflectMut, ReflectOwned, ReflectRef, Struct, Tuple, TypeInfo, Typed, - VariantFieldIter, VariantType, + enum_debug, enum_hash, enum_partial_eq, DynamicStruct, DynamicTuple, Enum, Reflect, ReflectMut, + ReflectOwned, ReflectRef, Struct, Tuple, TypeInfo, VariantFieldIter, VariantType, }; use std::any::Any; use std::fmt::Formatter; @@ -58,7 +56,6 @@ impl From<()> for DynamicVariant { /// /// // Create a DynamicEnum to represent the new value /// let mut dyn_enum = DynamicEnum::new( -/// Reflect::type_name(&value), /// "None", /// DynamicVariant::Unit /// ); @@ -71,7 +68,7 @@ impl From<()> for DynamicVariant { /// ``` #[derive(Default, Debug)] pub struct DynamicEnum { - name: String, + represented_type: Option<&'static TypeInfo>, variant_name: String, variant_index: usize, variant: DynamicVariant, @@ -82,17 +79,12 @@ impl DynamicEnum { /// /// # Arguments /// - /// * `name`: The type name of the enum /// * `variant_name`: The name of the variant to set /// * `variant`: The variant data /// - pub fn new, V: Into>( - name: I, - variant_name: I, - variant: V, - ) -> Self { + pub fn new, V: Into>(variant_name: I, variant: V) -> Self { Self { - name: name.into(), + represented_type: None, variant_index: 0, variant_name: variant_name.into(), variant: variant.into(), @@ -103,33 +95,40 @@ impl DynamicEnum { /// /// # Arguments /// - /// * `name`: The type name of the enum /// * `variant_index`: The index of the variant to set /// * `variant_name`: The name of the variant to set /// * `variant`: The variant data /// pub fn new_with_index, V: Into>( - name: I, variant_index: usize, variant_name: I, variant: V, ) -> Self { Self { - name: name.into(), + represented_type: None, variant_index, variant_name: variant_name.into(), variant: variant.into(), } } - /// Returns the type name of the enum. - pub fn name(&self) -> &str { - &self.name - } + /// Sets the [type] to be represented by this `DynamicEnum`. + /// + /// # Panics + /// + /// Panics if the given [type] is not a [`TypeInfo::Enum`]. + /// + /// [type]: TypeInfo + pub fn set_represented_type(&mut self, represented_type: Option<&'static TypeInfo>) { + if let Some(represented_type) = represented_type { + assert!( + matches!(represented_type, TypeInfo::Enum(_)), + "expected TypeInfo::Enum but received: {:?}", + represented_type + ); + } - /// Sets the type name of the enum. - pub fn set_name(&mut self, name: String) { - self.name = name; + self.represented_type = represented_type; } /// Set the current enum variant represented by this struct. @@ -142,11 +141,11 @@ impl DynamicEnum { pub fn set_variant_with_index, V: Into>( &mut self, variant_index: usize, - name: I, + variant_name: I, variant: V, ) { self.variant_index = variant_index; - self.variant_name = name.into(); + self.variant_name = variant_name.into(); self.variant = variant.into(); } @@ -161,9 +160,9 @@ impl DynamicEnum { /// /// This is functionally the same as [`DynamicEnum::from`] except it takes a reference. pub fn from_ref(value: &TEnum) -> Self { - match value.variant_type() { + let type_info = value.get_represented_type_info(); + let mut dyn_enum = match value.variant_type() { VariantType::Unit => DynamicEnum::new_with_index( - value.type_name(), value.variant_index(), value.variant_name(), DynamicVariant::Unit, @@ -174,7 +173,6 @@ impl DynamicEnum { data.insert_boxed(field.value().clone_value()); } DynamicEnum::new_with_index( - value.type_name(), value.variant_index(), value.variant_name(), DynamicVariant::Tuple(data), @@ -187,13 +185,15 @@ impl DynamicEnum { data.insert_boxed(name, field.value().clone_value()); } DynamicEnum::new_with_index( - value.type_name(), value.variant_index(), value.variant_name(), DynamicVariant::Struct(data), ) } - } + }; + + dyn_enum.set_represented_type(type_info); + dyn_enum } } @@ -276,7 +276,7 @@ impl Enum for DynamicEnum { fn clone_dynamic(&self) -> DynamicEnum { Self { - name: self.name.clone(), + represented_type: self.represented_type, variant_index: self.variant_index, variant_name: self.variant_name.clone(), variant: self.variant.clone(), @@ -287,12 +287,14 @@ impl Enum for DynamicEnum { impl Reflect for DynamicEnum { #[inline] fn type_name(&self) -> &str { - &self.name + self.represented_type + .map(|info| info.type_name()) + .unwrap_or_default() } #[inline] - fn get_type_info(&self) -> &'static TypeInfo { - ::type_info() + fn get_represented_type_info(&self) -> Option<&'static TypeInfo> { + self.represented_type } #[inline] @@ -418,10 +420,3 @@ impl Reflect for DynamicEnum { write!(f, ")") } } - -impl Typed for DynamicEnum { - fn type_info() -> &'static TypeInfo { - static CELL: NonGenericTypeInfoCell = NonGenericTypeInfoCell::new(); - CELL.get_or_set(|| TypeInfo::Dynamic(DynamicInfo::new::())) - } -} diff --git a/crates/bevy_reflect/src/enums/mod.rs b/crates/bevy_reflect/src/enums/mod.rs index e8c32e73ef..7693bf9794 100644 --- a/crates/bevy_reflect/src/enums/mod.rs +++ b/crates/bevy_reflect/src/enums/mod.rs @@ -342,14 +342,14 @@ mod tests { // === Tuple === // let mut data = DynamicTuple::default(); data.insert(1.23_f32); - let dyn_enum = DynamicEnum::new(std::any::type_name::>(), "B", data); + let dyn_enum = DynamicEnum::new("B", data); value.apply(&dyn_enum); assert_eq!(TestEnum::B(1.23), value); // === Struct === // let mut data = DynamicStruct::default(); data.insert("value", 1.23_f32); - let dyn_enum = DynamicEnum::new(std::any::type_name::>(), "C", data); + let dyn_enum = DynamicEnum::new("C", data); value.apply(&dyn_enum); assert_eq!(TestEnum::C { value: 1.23 }, value); } @@ -371,14 +371,14 @@ mod tests { // === Tuple === // let mut data = DynamicTuple::default(); data.insert(TestStruct(123)); - let dyn_enum = DynamicEnum::new(std::any::type_name::(), "B", data); + let dyn_enum = DynamicEnum::new("B", data); value.apply(&dyn_enum); assert_eq!(TestEnum::B(TestStruct(123)), value); // === Struct === // let mut data = DynamicStruct::default(); data.insert("value", TestStruct(123)); - let dyn_enum = DynamicEnum::new(std::any::type_name::(), "C", data); + let dyn_enum = DynamicEnum::new("C", data); value.apply(&dyn_enum); assert_eq!( TestEnum::C { @@ -409,14 +409,14 @@ mod tests { // === Tuple === // let mut data = DynamicTuple::default(); data.insert(OtherEnum::B(123)); - let dyn_enum = DynamicEnum::new(std::any::type_name::(), "B", data); + let dyn_enum = DynamicEnum::new("B", data); value.apply(&dyn_enum); assert_eq!(TestEnum::B(OtherEnum::B(123)), value); // === Struct === // let mut data = DynamicStruct::default(); data.insert("value", OtherEnum::C { value: 1.23 }); - let dyn_enum = DynamicEnum::new(std::any::type_name::(), "C", data); + let dyn_enum = DynamicEnum::new("C", data); value.apply(&dyn_enum); assert_eq!( TestEnum::C { diff --git a/crates/bevy_reflect/src/from_reflect.rs b/crates/bevy_reflect/src/from_reflect.rs index 4d98698398..f4abb65d7e 100644 --- a/crates/bevy_reflect/src/from_reflect.rs +++ b/crates/bevy_reflect/src/from_reflect.rs @@ -75,7 +75,7 @@ pub trait FromReflect: Reflect + Sized { /// # Example /// /// ``` -/// # use bevy_reflect::{DynamicTupleStruct, FromReflect, Reflect, ReflectFromReflect, TypeRegistry}; +/// # use bevy_reflect::{DynamicTupleStruct, FromReflect, Reflect, ReflectFromReflect, Typed, TypeRegistry}; /// # #[derive(Reflect, FromReflect, PartialEq, Eq, Debug)] /// # #[reflect(FromReflect)] /// # struct Foo(#[reflect(default = "default_value")] usize); @@ -84,7 +84,7 @@ pub trait FromReflect: Reflect + Sized { /// # registry.register::(); /// /// let mut reflected = DynamicTupleStruct::default(); -/// reflected.set_name(std::any::type_name::().to_string()); +/// reflected.set_represented_type(Some(::type_info())); /// /// let registration = registry.get_with_name(reflected.type_name()).unwrap(); /// let rfr = registration.data::().unwrap(); diff --git a/crates/bevy_reflect/src/impls/smallvec.rs b/crates/bevy_reflect/src/impls/smallvec.rs index 769c541d9d..918dc849b6 100644 --- a/crates/bevy_reflect/src/impls/smallvec.rs +++ b/crates/bevy_reflect/src/impls/smallvec.rs @@ -82,8 +82,8 @@ where std::any::type_name::() } - fn get_type_info(&self) -> &'static TypeInfo { - ::type_info() + fn get_represented_type_info(&self) -> Option<&'static TypeInfo> { + Some(::type_info()) } fn into_any(self: Box) -> Box { diff --git a/crates/bevy_reflect/src/impls/std.rs b/crates/bevy_reflect/src/impls/std.rs index 0b42a70793..db8baa3d9d 100644 --- a/crates/bevy_reflect/src/impls/std.rs +++ b/crates/bevy_reflect/src/impls/std.rs @@ -244,8 +244,8 @@ macro_rules! impl_reflect_for_veclike { std::any::type_name::() } - fn get_type_info(&self) -> &'static TypeInfo { - ::type_info() + fn get_represented_type_info(&self) -> Option<&'static TypeInfo> { + Some(::type_info()) } fn into_any(self: Box) -> Box { @@ -397,7 +397,7 @@ macro_rules! impl_reflect_for_hashmap { fn clone_dynamic(&self) -> DynamicMap { let mut dynamic_map = DynamicMap::default(); - dynamic_map.set_name(self.type_name().to_string()); + dynamic_map.set_represented_type(self.get_represented_type_info()); for (k, v) in self { let key = K::from_reflect(k).unwrap_or_else(|| { panic!("Attempted to clone invalid key of type {}.", k.type_name()) @@ -450,8 +450,8 @@ macro_rules! impl_reflect_for_hashmap { std::any::type_name::() } - fn get_type_info(&self) -> &'static TypeInfo { - ::type_info() + fn get_represented_type_info(&self) -> Option<&'static TypeInfo> { + Some(::type_info()) } fn into_any(self: Box) -> Box { @@ -595,8 +595,8 @@ impl Reflect for [T; N] { std::any::type_name::() } - fn get_type_info(&self) -> &'static TypeInfo { - ::type_info() + fn get_represented_type_info(&self) -> Option<&'static TypeInfo> { + Some(::type_info()) } #[inline] @@ -800,8 +800,8 @@ impl Reflect for Option { } #[inline] - fn get_type_info(&self) -> &'static TypeInfo { - ::type_info() + fn get_represented_type_info(&self) -> Option<&'static TypeInfo> { + Some(::type_info()) } #[inline] @@ -965,8 +965,8 @@ impl Reflect for Cow<'static, str> { std::any::type_name::() } - fn get_type_info(&self) -> &'static TypeInfo { - ::type_info() + fn get_represented_type_info(&self) -> Option<&'static TypeInfo> { + Some(::type_info()) } fn into_any(self: Box) -> Box { @@ -1073,8 +1073,8 @@ impl Reflect for &'static Path { std::any::type_name::() } - fn get_type_info(&self) -> &'static TypeInfo { - ::type_info() + fn get_represented_type_info(&self) -> Option<&'static TypeInfo> { + Some(::type_info()) } fn into_any(self: Box) -> Box { diff --git a/crates/bevy_reflect/src/lib.rs b/crates/bevy_reflect/src/lib.rs index 8b75615963..a2ef6d4af6 100644 --- a/crates/bevy_reflect/src/lib.rs +++ b/crates/bevy_reflect/src/lib.rs @@ -169,7 +169,7 @@ //! ``` //! # use bevy_reflect::{DynamicEnum, Reflect}; //! let mut value = Some(123_i32); -//! let patch = DynamicEnum::new(std::any::type_name::>(), "None", ()); +//! let patch = DynamicEnum::new("None", ()); //! value.apply(&patch); //! assert_eq!(None, value); //! ``` @@ -479,7 +479,7 @@ pub mod prelude { #[doc(hidden)] pub use crate::{ reflect_trait, FromReflect, GetField, GetTupleStructField, Reflect, ReflectDeserialize, - ReflectSerialize, Struct, TupleStruct, + ReflectFromReflect, ReflectSerialize, Struct, TupleStruct, }; } @@ -1100,7 +1100,7 @@ mod tests { // TypeInfo (instance) let value: &dyn Reflect = &123_i32; - let info = value.get_type_info(); + let info = value.get_represented_type_info().unwrap(); assert!(info.is::()); // Struct @@ -1133,7 +1133,7 @@ mod tests { } let value: &dyn Reflect = &MyStruct { foo: 123, bar: 321 }; - let info = value.get_type_info(); + let info = value.get_represented_type_info().unwrap(); assert!(info.is::()); // Struct (generic) @@ -1167,7 +1167,7 @@ mod tests { foo: String::from("Hello!"), bar: 321, }; - let info = value.get_type_info(); + let info = value.get_represented_type_info().unwrap(); assert!(info.is::>()); // Tuple Struct @@ -1203,7 +1203,7 @@ mod tests { } let value: &dyn Reflect = &(123_u32, 1.23_f32, String::from("Hello!")); - let info = value.get_type_info(); + let info = value.get_represented_type_info().unwrap(); assert!(info.is::()); // List @@ -1220,7 +1220,7 @@ mod tests { } let value: &dyn Reflect = &vec![123_usize]; - let info = value.get_type_info(); + let info = value.get_represented_type_info().unwrap(); assert!(info.is::()); // List (SmallVec) @@ -1240,7 +1240,7 @@ mod tests { let value: MySmallVec = smallvec::smallvec![String::default(); 2]; let value: &dyn Reflect = &value; - let info = value.get_type_info(); + let info = value.get_represented_type_info().unwrap(); assert!(info.is::()); } @@ -1259,7 +1259,7 @@ mod tests { } let value: &dyn Reflect = &[1usize, 2usize, 3usize]; - let info = value.get_type_info(); + let info = value.get_represented_type_info().unwrap(); assert!(info.is::()); // Map @@ -1278,7 +1278,7 @@ mod tests { } let value: &dyn Reflect = &MyMap::new(); - let info = value.get_type_info(); + let info = value.get_represented_type_info().unwrap(); assert!(info.is::()); // Value @@ -1293,23 +1293,23 @@ mod tests { } let value: &dyn Reflect = &String::from("Hello!"); - let info = value.get_type_info(); + let info = value.get_represented_type_info().unwrap(); assert!(info.is::()); + } - // Dynamic - type MyDynamic = DynamicList; + #[test] + fn should_permit_valid_represented_type_for_dynamic() { + let type_info = <[i32; 2] as Typed>::type_info(); + let mut dynamic_array = [123; 2].clone_dynamic(); + dynamic_array.set_represented_type(Some(type_info)); + } - let info = MyDynamic::type_info(); - if let TypeInfo::Dynamic(info) = info { - assert!(info.is::()); - assert_eq!(std::any::type_name::(), info.type_name()); - } else { - panic!("Expected `TypeInfo::Dynamic`"); - } - - let value: &dyn Reflect = &DynamicList::default(); - let info = value.get_type_info(); - assert!(info.is::()); + #[test] + #[should_panic(expected = "expected TypeInfo::Array but received")] + fn should_prohibit_invalid_represented_type_for_dynamic() { + let type_info = <(i32, i32) as Typed>::type_info(); + let mut dynamic_array = [123; 2].clone_dynamic(); + dynamic_array.set_represented_type(Some(type_info)); } #[cfg(feature = "documentation")] diff --git a/crates/bevy_reflect/src/list.rs b/crates/bevy_reflect/src/list.rs index d13e7776f0..6b17527302 100644 --- a/crates/bevy_reflect/src/list.rs +++ b/crates/bevy_reflect/src/list.rs @@ -2,10 +2,8 @@ use std::any::{Any, TypeId}; use std::fmt::{Debug, Formatter}; use std::hash::{Hash, Hasher}; -use crate::utility::{reflect_hasher, NonGenericTypeInfoCell}; -use crate::{ - DynamicInfo, FromReflect, Reflect, ReflectMut, ReflectOwned, ReflectRef, TypeInfo, Typed, -}; +use crate::utility::reflect_hasher; +use crate::{FromReflect, Reflect, ReflectMut, ReflectOwned, ReflectRef, TypeInfo}; /// A trait used to power [list-like] operations via [reflection]. /// @@ -97,7 +95,7 @@ pub trait List: Reflect { /// Clones the list, producing a [`DynamicList`]. fn clone_dynamic(&self) -> DynamicList { DynamicList { - name: self.type_name().to_string(), + represented_type: self.get_represented_type_info(), values: self.iter().map(|value| value.clone_value()).collect(), } } @@ -177,25 +175,27 @@ impl ListInfo { /// A list of reflected values. #[derive(Default)] pub struct DynamicList { - name: String, + represented_type: Option<&'static TypeInfo>, values: Vec>, } impl DynamicList { - /// Returns the type name of the list. + /// Sets the [type] to be represented by this `DynamicList`. + /// # Panics /// - /// The value returned by this method is the same value returned by - /// [`Reflect::type_name`]. - pub fn name(&self) -> &str { - &self.name - } + /// Panics if the given [type] is not a [`TypeInfo::List`]. + /// + /// [type]: TypeInfo + pub fn set_represented_type(&mut self, represented_type: Option<&'static TypeInfo>) { + if let Some(represented_type) = represented_type { + assert!( + matches!(represented_type, TypeInfo::List(_)), + "expected TypeInfo::List but received: {:?}", + represented_type + ); + } - /// Sets the type name of the list. - /// - /// The value set by this method is the value returned by - /// [`Reflect::type_name`]. - pub fn set_name(&mut self, name: String) { - self.name = name; + self.represented_type = represented_type; } /// Appends a typed value to the list. @@ -248,7 +248,7 @@ impl List for DynamicList { fn clone_dynamic(&self) -> DynamicList { DynamicList { - name: self.name.clone(), + represented_type: self.represented_type, values: self .values .iter() @@ -261,12 +261,14 @@ impl List for DynamicList { impl Reflect for DynamicList { #[inline] fn type_name(&self) -> &str { - self.name.as_str() + self.represented_type + .map(|info| info.type_name()) + .unwrap_or_else(|| std::any::type_name::()) } #[inline] - fn get_type_info(&self) -> &'static TypeInfo { - ::type_info() + fn get_represented_type_info(&self) -> Option<&'static TypeInfo> { + self.represented_type } #[inline] @@ -343,6 +345,11 @@ impl Reflect for DynamicList { list_debug(self, f)?; write!(f, ")") } + + #[inline] + fn is_dynamic(&self) -> bool { + true + } } impl Debug for DynamicList { @@ -351,13 +358,6 @@ impl Debug for DynamicList { } } -impl Typed for DynamicList { - fn type_info() -> &'static TypeInfo { - static CELL: NonGenericTypeInfoCell = NonGenericTypeInfoCell::new(); - CELL.get_or_set(|| TypeInfo::Dynamic(DynamicInfo::new::())) - } -} - impl IntoIterator for DynamicList { type Item = Box; type IntoIter = std::vec::IntoIter; diff --git a/crates/bevy_reflect/src/map.rs b/crates/bevy_reflect/src/map.rs index 8dcd28386a..6e997ffd32 100644 --- a/crates/bevy_reflect/src/map.rs +++ b/crates/bevy_reflect/src/map.rs @@ -4,8 +4,7 @@ use std::hash::Hash; use bevy_utils::{Entry, HashMap}; -use crate::utility::NonGenericTypeInfoCell; -use crate::{DynamicInfo, Reflect, ReflectMut, ReflectOwned, ReflectRef, TypeInfo, Typed}; +use crate::{Reflect, ReflectMut, ReflectOwned, ReflectRef, TypeInfo}; /// A trait used to power [map-like] operations via [reflection]. /// @@ -184,26 +183,29 @@ const HASH_ERROR: &str = "the given key does not support hashing"; /// An ordered mapping between reflected values. #[derive(Default)] pub struct DynamicMap { - name: String, + represented_type: Option<&'static TypeInfo>, values: Vec<(Box, Box)>, indices: HashMap, } impl DynamicMap { - /// Returns the type name of the map. + /// Sets the [type] to be represented by this `DynamicMap`. /// - /// The value returned by this method is the same value returned by - /// [`Reflect::type_name`]. - pub fn name(&self) -> &str { - &self.name - } + /// # Panics + /// + /// Panics if the given [type] is not a [`TypeInfo::Map`]. + /// + /// [type]: TypeInfo + pub fn set_represented_type(&mut self, represented_type: Option<&'static TypeInfo>) { + if let Some(represented_type) = represented_type { + assert!( + matches!(represented_type, TypeInfo::Map(_)), + "expected TypeInfo::Map but received: {:?}", + represented_type + ); + } - /// Sets the type name of the map. - /// - /// The value set by this method is the same value returned by - /// [`Reflect::type_name`]. - pub fn set_name(&mut self, name: String) { - self.name = name; + self.represented_type = represented_type; } /// Inserts a typed key-value pair into the map. @@ -232,7 +234,7 @@ impl Map for DynamicMap { fn clone_dynamic(&self) -> DynamicMap { DynamicMap { - name: self.name.clone(), + represented_type: self.represented_type, values: self .values .iter() @@ -289,12 +291,14 @@ impl Map for DynamicMap { impl Reflect for DynamicMap { fn type_name(&self) -> &str { - &self.name + self.represented_type + .map(|info| info.type_name()) + .unwrap_or_else(|| std::any::type_name::()) } #[inline] - fn get_type_info(&self) -> &'static TypeInfo { - ::type_info() + fn get_represented_type_info(&self) -> Option<&'static TypeInfo> { + self.represented_type } fn into_any(self: Box) -> Box { @@ -358,6 +362,11 @@ impl Reflect for DynamicMap { map_debug(self, f)?; write!(f, ")") } + + #[inline] + fn is_dynamic(&self) -> bool { + true + } } impl Debug for DynamicMap { @@ -366,13 +375,6 @@ impl Debug for DynamicMap { } } -impl Typed for DynamicMap { - fn type_info() -> &'static TypeInfo { - static CELL: NonGenericTypeInfoCell = NonGenericTypeInfoCell::new(); - CELL.get_or_set(|| TypeInfo::Dynamic(DynamicInfo::new::())) - } -} - /// An iterator over the key-value pairs of a [`Map`]. pub struct MapIter<'a> { pub(crate) map: &'a dyn Map, diff --git a/crates/bevy_reflect/src/reflect.rs b/crates/bevy_reflect/src/reflect.rs index 8abfeef742..c0749f5067 100644 --- a/crates/bevy_reflect/src/reflect.rs +++ b/crates/bevy_reflect/src/reflect.rs @@ -76,15 +76,22 @@ pub trait Reflect: Any + Send + Sync { /// Returns the [type name][std::any::type_name] of the underlying type. fn type_name(&self) -> &str; - /// Returns the [`TypeInfo`] of the underlying type. + /// Returns the [`TypeInfo`] of the type _represented_ by this value. + /// + /// For most types, this will simply return their own `TypeInfo`. + /// However, for dynamic types, such as [`DynamicStruct`] or [`DynamicList`], + /// this will return the type they represent + /// (or `None` if they don't represent any particular type). /// /// This method is great if you have an instance of a type or a `dyn Reflect`, /// and want to access its [`TypeInfo`]. However, if this method is to be called /// frequently, consider using [`TypeRegistry::get_type_info`] as it can be more /// performant for such use cases. /// + /// [`DynamicStruct`]: crate::DynamicStruct + /// [`DynamicList`]: crate::DynamicList /// [`TypeRegistry::get_type_info`]: crate::TypeRegistry::get_type_info - fn get_type_info(&self) -> &'static TypeInfo; + fn get_represented_type_info(&self) -> Option<&'static TypeInfo>; /// Returns the value as a [`Box`][std::any::Any]. fn into_any(self: Box) -> Box; @@ -216,6 +223,22 @@ pub trait Reflect: Any + Send + Sync { fn serializable(&self) -> Option { None } + + /// Indicates whether or not this type is a _dynamic_ type. + /// + /// Dynamic types include the ones built-in to this [crate], + /// such as [`DynamicStruct`], [`DynamicList`], and [`DynamicTuple`]. + /// However, they may be custom types used as proxies for other types + /// or to facilitate scripting capabilities. + /// + /// By default, this method will return `false`. + /// + /// [`DynamicStruct`]: crate::DynamicStruct + /// [`DynamicList`]: crate::DynamicList + /// [`DynamicTuple`]: crate::DynamicTuple + fn is_dynamic(&self) -> bool { + false + } } impl Debug for dyn Reflect { diff --git a/crates/bevy_reflect/src/serde/de.rs b/crates/bevy_reflect/src/serde/de.rs index 60d3b0aa20..de7c474381 100644 --- a/crates/bevy_reflect/src/serde/de.rs +++ b/crates/bevy_reflect/src/serde/de.rs @@ -389,7 +389,7 @@ impl<'a, 'de> DeserializeSeed<'de> for TypedReflectDeserializer<'a> { registry: self.registry, }, )?; - dynamic_struct.set_name(struct_info.type_name().to_string()); + dynamic_struct.set_represented_type(Some(self.registration.type_info())); Ok(Box::new(dynamic_struct)) } TypeInfo::TupleStruct(tuple_struct_info) => { @@ -402,7 +402,7 @@ impl<'a, 'de> DeserializeSeed<'de> for TypedReflectDeserializer<'a> { registration: self.registration, }, )?; - dynamic_tuple_struct.set_name(tuple_struct_info.type_name().to_string()); + dynamic_tuple_struct.set_represented_type(Some(self.registration.type_info())); Ok(Box::new(dynamic_tuple_struct)) } TypeInfo::List(list_info) => { @@ -410,7 +410,7 @@ impl<'a, 'de> DeserializeSeed<'de> for TypedReflectDeserializer<'a> { list_info, registry: self.registry, })?; - dynamic_list.set_name(list_info.type_name().to_string()); + dynamic_list.set_represented_type(Some(self.registration.type_info())); Ok(Box::new(dynamic_list)) } TypeInfo::Array(array_info) => { @@ -421,7 +421,7 @@ impl<'a, 'de> DeserializeSeed<'de> for TypedReflectDeserializer<'a> { registry: self.registry, }, )?; - dynamic_array.set_name(array_info.type_name().to_string()); + dynamic_array.set_represented_type(Some(self.registration.type_info())); Ok(Box::new(dynamic_array)) } TypeInfo::Map(map_info) => { @@ -429,7 +429,7 @@ impl<'a, 'de> DeserializeSeed<'de> for TypedReflectDeserializer<'a> { map_info, registry: self.registry, })?; - dynamic_map.set_name(map_info.type_name().to_string()); + dynamic_map.set_represented_type(Some(self.registration.type_info())); Ok(Box::new(dynamic_map)) } TypeInfo::Tuple(tuple_info) => { @@ -440,7 +440,7 @@ impl<'a, 'de> DeserializeSeed<'de> for TypedReflectDeserializer<'a> { registry: self.registry, }, )?; - dynamic_tuple.set_name(tuple_info.type_name().to_string()); + dynamic_tuple.set_represented_type(Some(self.registration.type_info())); Ok(Box::new(dynamic_tuple)) } TypeInfo::Enum(enum_info) => { @@ -461,7 +461,7 @@ impl<'a, 'de> DeserializeSeed<'de> for TypedReflectDeserializer<'a> { }, )? }; - dynamic_enum.set_name(type_name.to_string()); + dynamic_enum.set_represented_type(Some(self.registration.type_info())); Ok(Box::new(dynamic_enum)) } TypeInfo::Value(_) => { @@ -470,13 +470,6 @@ impl<'a, 'de> DeserializeSeed<'de> for TypedReflectDeserializer<'a> { "the TypeRegistration for {type_name} doesn't have ReflectDeserialize", ))) } - TypeInfo::Dynamic(_) => { - // We could potentially allow this but we'd have no idea what the actual types of the - // fields are and would rely on the deserializer to determine them (e.g. `i32` vs `i64`) - Err(de::Error::custom(format_args!( - "cannot deserialize arbitrary dynamic type {type_name}", - ))) - } } } } diff --git a/crates/bevy_reflect/src/serde/mod.rs b/crates/bevy_reflect/src/serde/mod.rs index 3355ed38d0..58231b2654 100644 --- a/crates/bevy_reflect/src/serde/mod.rs +++ b/crates/bevy_reflect/src/serde/mod.rs @@ -92,4 +92,16 @@ mod tests { "Expected {expected:?} found {deserialized:?}" ); } + + #[test] + #[should_panic(expected = "cannot get type info for bevy_reflect::struct_trait::DynamicStruct")] + fn unproxied_dynamic_should_not_serialize() { + let registry = TypeRegistry::default(); + + let mut value = DynamicStruct::default(); + value.insert("foo", 123_u32); + + let serializer = ReflectSerializer::new(&value, ®istry); + ron::ser::to_string(&serializer).unwrap(); + } } diff --git a/crates/bevy_reflect/src/serde/ser.rs b/crates/bevy_reflect/src/serde/ser.rs index e9e4abe1d8..38d4529a3a 100644 --- a/crates/bevy_reflect/src/serde/ser.rs +++ b/crates/bevy_reflect/src/serde/ser.rs @@ -43,26 +43,6 @@ fn get_serializable<'a, E: serde::ser::Error>( Ok(reflect_serialize.get_serializable(reflect_value)) } -/// Get the underlying [`TypeInfo`] of a given type. -/// -/// If the given type is a [`TypeInfo::Dynamic`] then we need to try and look -/// up the actual type in the registry. -fn get_type_info( - type_info: &'static TypeInfo, - type_name: &str, - registry: &TypeRegistry, -) -> Result<&'static TypeInfo, E> { - match type_info { - TypeInfo::Dynamic(..) => match registry.get_with_name(type_name) { - Some(registration) => Ok(registration.type_info()), - None => Err(Error::custom(format_args!( - "no registration found for dynamic type with name {type_name}", - ))), - }, - info => Ok(info), - } -} - /// A general purpose serializer for reflected types. /// /// The serialized data will take the form of a map containing the following entries: @@ -186,11 +166,15 @@ impl<'a> Serialize for StructSerializer<'a> { where S: serde::Serializer, { - let type_info = get_type_info( - self.struct_value.get_type_info(), - self.struct_value.type_name(), - self.registry, - )?; + let type_info = self + .struct_value + .get_represented_type_info() + .ok_or_else(|| { + Error::custom(format_args!( + "cannot get type info for {}", + self.struct_value.type_name() + )) + })?; let struct_info = match type_info { TypeInfo::Struct(struct_info) => struct_info, @@ -235,11 +219,15 @@ impl<'a> Serialize for TupleStructSerializer<'a> { where S: serde::Serializer, { - let type_info = get_type_info( - self.tuple_struct.get_type_info(), - self.tuple_struct.type_name(), - self.registry, - )?; + let type_info = self + .tuple_struct + .get_represented_type_info() + .ok_or_else(|| { + Error::custom(format_args!( + "cannot get type info for {}", + self.tuple_struct.type_name() + )) + })?; let tuple_struct_info = match type_info { TypeInfo::TupleStruct(tuple_struct_info) => tuple_struct_info, @@ -283,11 +271,12 @@ impl<'a> Serialize for EnumSerializer<'a> { where S: serde::Serializer, { - let type_info = get_type_info( - self.enum_value.get_type_info(), - self.enum_value.type_name(), - self.registry, - )?; + let type_info = self.enum_value.get_represented_type_info().ok_or_else(|| { + Error::custom(format_args!( + "cannot get type info for {}", + self.enum_value.type_name() + )) + })?; let enum_info = match type_info { TypeInfo::Enum(enum_info) => enum_info, diff --git a/crates/bevy_reflect/src/struct_trait.rs b/crates/bevy_reflect/src/struct_trait.rs index ab146d1b36..ede4ddec4d 100644 --- a/crates/bevy_reflect/src/struct_trait.rs +++ b/crates/bevy_reflect/src/struct_trait.rs @@ -1,7 +1,4 @@ -use crate::utility::NonGenericTypeInfoCell; -use crate::{ - DynamicInfo, NamedField, Reflect, ReflectMut, ReflectOwned, ReflectRef, TypeInfo, Typed, -}; +use crate::{NamedField, Reflect, ReflectMut, ReflectOwned, ReflectRef, TypeInfo}; use bevy_utils::{Entry, HashMap}; use std::fmt::{Debug, Formatter}; use std::{ @@ -272,21 +269,30 @@ impl GetField for dyn Struct { /// A struct type which allows fields to be added at runtime. #[derive(Default)] pub struct DynamicStruct { - name: String, + represented_type: Option<&'static TypeInfo>, fields: Vec>, field_names: Vec>, field_indices: HashMap, usize>, } impl DynamicStruct { - /// Returns the type name of the struct. - pub fn name(&self) -> &str { - &self.name - } + /// Sets the [type] to be represented by this `DynamicStruct`. + /// + /// # Panics + /// + /// Panics if the given [type] is not a [`TypeInfo::Struct`]. + /// + /// [type]: TypeInfo + pub fn set_represented_type(&mut self, represented_type: Option<&'static TypeInfo>) { + if let Some(represented_type) = represented_type { + assert!( + matches!(represented_type, TypeInfo::Struct(_)), + "expected TypeInfo::Struct but received: {:?}", + represented_type + ); + } - /// Sets the type name of the struct. - pub fn set_name(&mut self, name: String) { - self.name = name; + self.represented_type = represented_type; } /// Inserts a field named `name` with value `value` into the struct. @@ -370,7 +376,7 @@ impl Struct for DynamicStruct { fn clone_dynamic(&self) -> DynamicStruct { DynamicStruct { - name: self.name.clone(), + represented_type: self.get_represented_type_info(), field_names: self.field_names.clone(), field_indices: self.field_indices.clone(), fields: self @@ -385,12 +391,14 @@ impl Struct for DynamicStruct { impl Reflect for DynamicStruct { #[inline] fn type_name(&self) -> &str { - &self.name + self.represented_type + .map(|info| info.type_name()) + .unwrap_or_else(|| std::any::type_name::()) } #[inline] - fn get_type_info(&self) -> &'static TypeInfo { - ::type_info() + fn get_represented_type_info(&self) -> Option<&'static TypeInfo> { + self.represented_type } #[inline] @@ -470,6 +478,11 @@ impl Reflect for DynamicStruct { struct_debug(self, f)?; write!(f, ")") } + + #[inline] + fn is_dynamic(&self) -> bool { + true + } } impl Debug for DynamicStruct { @@ -478,13 +491,6 @@ impl Debug for DynamicStruct { } } -impl Typed for DynamicStruct { - fn type_info() -> &'static TypeInfo { - static CELL: NonGenericTypeInfoCell = NonGenericTypeInfoCell::new(); - CELL.get_or_set(|| TypeInfo::Dynamic(DynamicInfo::new::())) - } -} - /// Compares a [`Struct`] with a [`Reflect`] value. /// /// Returns true if and only if all of the following are true: diff --git a/crates/bevy_reflect/src/tuple.rs b/crates/bevy_reflect/src/tuple.rs index f0e20aff6b..8f0340bcbb 100644 --- a/crates/bevy_reflect/src/tuple.rs +++ b/crates/bevy_reflect/src/tuple.rs @@ -1,9 +1,9 @@ -use crate::utility::NonGenericTypeInfoCell; use crate::{ - DynamicInfo, FromReflect, GetTypeRegistration, Reflect, ReflectMut, ReflectOwned, ReflectRef, - TypeInfo, TypeRegistration, Typed, UnnamedField, + FromReflect, GetTypeRegistration, Reflect, ReflectMut, ReflectOwned, ReflectRef, TypeInfo, + TypeRegistration, Typed, UnnamedField, }; use std::any::{Any, TypeId}; +use std::borrow::Cow; use std::fmt::{Debug, Formatter}; use std::slice::Iter; @@ -207,39 +207,48 @@ impl TupleInfo { /// A tuple which allows fields to be added at runtime. #[derive(Default, Debug)] pub struct DynamicTuple { - name: String, + name: Cow<'static, str>, + represented_type: Option<&'static TypeInfo>, fields: Vec>, } impl DynamicTuple { - /// Returns the type name of the tuple. + /// Sets the [type] to be represented by this `DynamicTuple`. /// - /// The tuple's name is automatically generated from its element types. - pub fn name(&self) -> &str { - &self.name - } + /// # Panics + /// + /// Panics if the given [type] is not a [`TypeInfo::Tuple`]. + /// + /// [type]: TypeInfo + pub fn set_represented_type(&mut self, represented_type: Option<&'static TypeInfo>) { + if let Some(represented_type) = represented_type { + assert!( + matches!(represented_type, TypeInfo::Tuple(_)), + "expected TypeInfo::Tuple but received: {:?}", + represented_type + ); - /// Manually sets the type name of the tuple. - /// - /// Note that the tuple name will be overwritten when elements are added. - pub fn set_name(&mut self, name: String) { - self.name = name; + self.name = Cow::Borrowed(represented_type.type_name()); + } + self.represented_type = represented_type; } /// Appends an element with value `value` to the tuple. pub fn insert_boxed(&mut self, value: Box) { + self.represented_type = None; self.fields.push(value); self.generate_name(); } /// Appends a typed element with value `value` to the tuple. pub fn insert(&mut self, value: T) { + self.represented_type = None; self.insert_boxed(Box::new(value)); self.generate_name(); } fn generate_name(&mut self) { - let name = &mut self.name; + let mut name = self.name.to_string(); name.clear(); name.push('('); for (i, field) in self.fields.iter().enumerate() { @@ -249,6 +258,7 @@ impl DynamicTuple { name.push_str(field.type_name()); } name.push(')'); + self.name = Cow::Owned(name); } } @@ -285,6 +295,7 @@ impl Tuple for DynamicTuple { fn clone_dynamic(&self) -> DynamicTuple { DynamicTuple { name: self.name.clone(), + represented_type: self.represented_type, fields: self .fields .iter() @@ -297,12 +308,14 @@ impl Tuple for DynamicTuple { impl Reflect for DynamicTuple { #[inline] fn type_name(&self) -> &str { - self.name() + self.represented_type + .map(|info| info.type_name()) + .unwrap_or_else(|| &self.name) } #[inline] - fn get_type_info(&self) -> &'static TypeInfo { - ::type_info() + fn get_represented_type_info(&self) -> Option<&'static TypeInfo> { + self.represented_type } #[inline] @@ -373,12 +386,10 @@ impl Reflect for DynamicTuple { tuple_debug(self, f)?; write!(f, ")") } -} -impl Typed for DynamicTuple { - fn type_info() -> &'static TypeInfo { - static CELL: NonGenericTypeInfoCell = NonGenericTypeInfoCell::new(); - CELL.get_or_set(|| TypeInfo::Dynamic(DynamicInfo::new::())) + #[inline] + fn is_dynamic(&self) -> bool { + true } } @@ -496,15 +507,15 @@ macro_rules! impl_reflect_tuple { #[inline] fn clone_dynamic(&self) -> DynamicTuple { - let mut dyn_tuple = DynamicTuple { - name: String::default(), + let info = self.get_represented_type_info(); + DynamicTuple { + name: Cow::Borrowed(::core::any::type_name::()), + represented_type: info, fields: self .iter_fields() .map(|value| value.clone_value()) .collect(), - }; - dyn_tuple.generate_name(); - dyn_tuple + } } } @@ -513,8 +524,8 @@ macro_rules! impl_reflect_tuple { std::any::type_name::() } - fn get_type_info(&self) -> &'static TypeInfo { - ::type_info() + fn get_represented_type_info(&self) -> Option<&'static TypeInfo> { + Some(::type_info()) } fn into_any(self: Box) -> Box { diff --git a/crates/bevy_reflect/src/tuple_struct.rs b/crates/bevy_reflect/src/tuple_struct.rs index 33edd7bb17..9b7466d575 100644 --- a/crates/bevy_reflect/src/tuple_struct.rs +++ b/crates/bevy_reflect/src/tuple_struct.rs @@ -1,7 +1,4 @@ -use crate::utility::NonGenericTypeInfoCell; -use crate::{ - DynamicInfo, Reflect, ReflectMut, ReflectOwned, ReflectRef, TypeInfo, Typed, UnnamedField, -}; +use crate::{Reflect, ReflectMut, ReflectOwned, ReflectRef, TypeInfo, UnnamedField}; use std::any::{Any, TypeId}; use std::fmt::{Debug, Formatter}; use std::slice::Iter; @@ -222,19 +219,28 @@ impl GetTupleStructField for dyn TupleStruct { /// A tuple struct which allows fields to be added at runtime. #[derive(Default)] pub struct DynamicTupleStruct { - name: String, + represented_type: Option<&'static TypeInfo>, fields: Vec>, } impl DynamicTupleStruct { - /// Returns the type name of the tuple struct. - pub fn name(&self) -> &str { - &self.name - } + /// Sets the [type] to be represented by this `DynamicTupleStruct`. + /// + /// # Panics + /// + /// Panics if the given [type] is not a [`TypeInfo::TupleStruct`]. + /// + /// [type]: TypeInfo + pub fn set_represented_type(&mut self, represented_type: Option<&'static TypeInfo>) { + if let Some(represented_type) = represented_type { + assert!( + matches!(represented_type, TypeInfo::TupleStruct(_)), + "expected TypeInfo::TupleStruct but received: {:?}", + represented_type + ); + } - /// Sets the type name of the tuple struct. - pub fn set_name(&mut self, name: String) { - self.name = name; + self.represented_type = represented_type; } /// Appends an element with value `value` to the tuple struct. @@ -274,7 +280,7 @@ impl TupleStruct for DynamicTupleStruct { fn clone_dynamic(&self) -> DynamicTupleStruct { DynamicTupleStruct { - name: self.name.clone(), + represented_type: self.represented_type, fields: self .fields .iter() @@ -287,12 +293,14 @@ impl TupleStruct for DynamicTupleStruct { impl Reflect for DynamicTupleStruct { #[inline] fn type_name(&self) -> &str { - self.name.as_str() + self.represented_type + .map(|info| info.type_name()) + .unwrap_or_else(|| std::any::type_name::()) } #[inline] - fn get_type_info(&self) -> &'static TypeInfo { - ::type_info() + fn get_represented_type_info(&self) -> Option<&'static TypeInfo> { + self.represented_type } #[inline] @@ -371,6 +379,11 @@ impl Reflect for DynamicTupleStruct { tuple_struct_debug(self, f)?; write!(f, ")") } + + #[inline] + fn is_dynamic(&self) -> bool { + true + } } impl Debug for DynamicTupleStruct { @@ -379,13 +392,6 @@ impl Debug for DynamicTupleStruct { } } -impl Typed for DynamicTupleStruct { - fn type_info() -> &'static TypeInfo { - static CELL: NonGenericTypeInfoCell = NonGenericTypeInfoCell::new(); - CELL.get_or_set(|| TypeInfo::Dynamic(DynamicInfo::new::())) - } -} - /// Compares a [`TupleStruct`] with a [`Reflect`] value. /// /// Returns true if and only if all of the following are true: diff --git a/crates/bevy_reflect/src/type_info.rs b/crates/bevy_reflect/src/type_info.rs index 27df34ea76..c99beece3a 100644 --- a/crates/bevy_reflect/src/type_info.rs +++ b/crates/bevy_reflect/src/type_info.rs @@ -2,6 +2,7 @@ use crate::{ ArrayInfo, EnumInfo, ListInfo, MapInfo, Reflect, StructInfo, TupleInfo, TupleStructInfo, }; use std::any::{Any, TypeId}; +use std::fmt::Debug; /// A static accessor to compile-time type information. /// @@ -49,7 +50,7 @@ use std::any::{Any, TypeId}; /// # /// # impl Reflect for MyStruct { /// # fn type_name(&self) -> &str { todo!() } -/// # fn get_type_info(&self) -> &'static TypeInfo { todo!() } +/// # fn get_represented_type_info(&self) -> Option<&'static TypeInfo> { todo!() } /// # fn into_any(self: Box) -> Box { todo!() } /// # fn as_any(&self) -> &dyn Any { todo!() } /// # fn as_any_mut(&mut self) -> &mut dyn Any { todo!() } @@ -78,12 +79,12 @@ pub trait Typed: Reflect { /// Generally, for any given type, this value can be retrieved one of three ways: /// /// 1. [`Typed::type_info`] -/// 2. [`Reflect::get_type_info`] +/// 2. [`Reflect::get_represented_type_info`] /// 3. [`TypeRegistry::get_type_info`] /// /// Each return a static reference to [`TypeInfo`], but they all have their own use cases. /// For example, if you know the type at compile time, [`Typed::type_info`] is probably -/// the simplest. If all you have is a `dyn Reflect`, you'll probably want [`Reflect::get_type_info`]. +/// the simplest. If all you have is a `dyn Reflect`, you'll probably want [`Reflect::get_represented_type_info`]. /// Lastly, if all you have is a [`TypeId`] or [type name], you will need to go through /// [`TypeRegistry::get_type_info`]. /// @@ -91,7 +92,7 @@ pub trait Typed: Reflect { /// it can be more performant. This is because those other methods may require attaining a lock on /// the static [`TypeInfo`], while the registry simply checks a map. /// -/// [`Reflect::get_type_info`]: crate::Reflect::get_type_info +/// [`Reflect::get_represented_type_info`]: crate::Reflect::get_represented_type_info /// [`TypeRegistry::get_type_info`]: crate::TypeRegistry::get_type_info /// [`TypeId`]: std::any::TypeId /// [type name]: std::any::type_name @@ -105,10 +106,6 @@ pub enum TypeInfo { Map(MapInfo), Enum(EnumInfo), Value(ValueInfo), - /// Type information for "dynamic" types whose metadata can't be known at compile-time. - /// - /// This includes structs like [`DynamicStruct`](crate::DynamicStruct) and [`DynamicList`](crate::DynamicList). - Dynamic(DynamicInfo), } impl TypeInfo { @@ -123,7 +120,6 @@ impl TypeInfo { Self::Map(info) => info.type_id(), Self::Enum(info) => info.type_id(), Self::Value(info) => info.type_id(), - Self::Dynamic(info) => info.type_id(), } } @@ -140,7 +136,6 @@ impl TypeInfo { Self::Map(info) => info.type_name(), Self::Enum(info) => info.type_name(), Self::Value(info) => info.type_name(), - Self::Dynamic(info) => info.type_name(), } } @@ -161,7 +156,6 @@ impl TypeInfo { Self::Map(info) => info.docs(), Self::Enum(info) => info.docs(), Self::Value(info) => info.docs(), - Self::Dynamic(info) => info.docs(), } } } @@ -221,59 +215,3 @@ impl ValueInfo { self.docs } } - -/// A container for compile-time info related to Bevy's _dynamic_ types, including primitives. -/// -/// This is functionally the same as [`ValueInfo`], however, semantically it refers to dynamic -/// types such as [`DynamicStruct`], [`DynamicTuple`], [`DynamicList`], etc. -/// -/// [`DynamicStruct`]: crate::DynamicStruct -/// [`DynamicTuple`]: crate::DynamicTuple -/// [`DynamicList`]: crate::DynamicList -#[derive(Debug, Clone)] -pub struct DynamicInfo { - type_name: &'static str, - type_id: TypeId, - #[cfg(feature = "documentation")] - docs: Option<&'static str>, -} - -impl DynamicInfo { - pub fn new() -> Self { - Self { - type_name: std::any::type_name::(), - type_id: TypeId::of::(), - #[cfg(feature = "documentation")] - docs: None, - } - } - - /// Sets the docstring for this dynamic value. - #[cfg(feature = "documentation")] - pub fn with_docs(self, docs: Option<&'static str>) -> Self { - Self { docs, ..self } - } - - /// The [type name] of the dynamic value. - /// - /// [type name]: std::any::type_name - pub fn type_name(&self) -> &'static str { - self.type_name - } - - /// The [`TypeId`] of the dynamic value. - pub fn type_id(&self) -> TypeId { - self.type_id - } - - /// Check if the given type matches the dynamic value type. - pub fn is(&self) -> bool { - TypeId::of::() == self.type_id - } - - /// The docstring of this value, if any. - #[cfg(feature = "documentation")] - pub fn docs(&self) -> Option<&'static str> { - self.docs - } -} diff --git a/crates/bevy_reflect/src/utility.rs b/crates/bevy_reflect/src/utility.rs index 3af535a192..7c203631aa 100644 --- a/crates/bevy_reflect/src/utility.rs +++ b/crates/bevy_reflect/src/utility.rs @@ -39,7 +39,7 @@ use std::{ /// # /// # impl Reflect for Foo { /// # fn type_name(&self) -> &str { todo!() } -/// # fn get_type_info(&self) -> &'static TypeInfo { todo!() } +/// # fn get_represented_type_info(&self) -> Option<&'static TypeInfo> { todo!() } /// # fn into_any(self: Box) -> Box { todo!() } /// # fn as_any(&self) -> &dyn Any { todo!() } /// # fn as_any_mut(&mut self) -> &mut dyn Any { todo!() } @@ -102,7 +102,7 @@ impl NonGenericTypeInfoCell { /// # /// # impl Reflect for Foo { /// # fn type_name(&self) -> &str { todo!() } -/// # fn get_type_info(&self) -> &'static TypeInfo { todo!() } +/// # fn get_represented_type_info(&self) -> Option<&'static TypeInfo> { todo!() } /// # fn into_any(self: Box) -> Box { todo!() } /// # fn as_any(&self) -> &dyn Any { todo!() } /// # fn as_any_mut(&mut self) -> &mut dyn Any { todo!() } diff --git a/crates/bevy_render/Cargo.toml b/crates/bevy_render/Cargo.toml index e10f7f6da5..aec631dde0 100644 --- a/crates/bevy_render/Cargo.toml +++ b/crates/bevy_render/Cargo.toml @@ -17,6 +17,10 @@ jpeg = ["image/jpeg"] bmp = ["image/bmp"] webp = ["image/webp"] dds = ["ddsfile"] +bevy_ci_testing = ["bevy_app/bevy_ci_testing"] + +shader_format_glsl = ["naga/glsl-in", "naga/wgsl-out"] +shader_format_spirv = ["wgpu/spirv", "naga/spv-in", "naga/spv-out"] # For ktx2 supercompression zlib = ["flate2"] @@ -52,10 +56,10 @@ bevy_tasks = { path = "../bevy_tasks", version = "0.11.0-dev" } image = { version = "0.24", default-features = false } # misc -wgpu = { version = "0.15.0", features = ["spirv"] } -wgpu-hal = "0.15.1" +wgpu = { version = "0.16.0" } +wgpu-hal = "0.16.0" codespan-reporting = "0.11.0" -naga = { version = "0.11.0", features = ["glsl-in", "spv-in", "spv-out", "wgsl-in", "wgsl-out"] } +naga = { version = "0.12.0", features = ["wgsl-in"] } serde = { version = "1", features = ["derive"] } bitflags = "1.2.1" smallvec = { version = "1.6", features = ["union", "const_generics"] } @@ -79,3 +83,16 @@ encase = { version = "0.5", features = ["glam"] } # For wgpu profiling using tracing. Use `RUST_LOG=info` to also capture the wgpu spans. profiling = { version = "1", features = ["profile-with-tracing"], optional = true } async-channel = "1.8" + +[target.'cfg(target_arch = "wasm32")'.dependencies] +js-sys = "0.3" +web-sys = { version = "0.3", features = [ + 'Blob', + 'Document', + 'Element', + 'HtmlElement', + 'Node', + 'Url', + 'Window', +] } +wasm-bindgen = "0.2" diff --git a/crates/bevy_render/src/mesh/mesh/skinning.rs b/crates/bevy_render/src/mesh/mesh/skinning.rs index 3c3cef062d..a124655df1 100644 --- a/crates/bevy_render/src/mesh/mesh/skinning.rs +++ b/crates/bevy_render/src/mesh/mesh/skinning.rs @@ -1,7 +1,7 @@ use bevy_asset::Handle; use bevy_ecs::{ component::Component, - entity::{Entity, EntityMap, MapEntities, MapEntitiesError}, + entity::{Entity, EntityMapper, MapEntities}, prelude::ReflectComponent, reflect::ReflectMapEntities, }; @@ -17,12 +17,10 @@ pub struct SkinnedMesh { } impl MapEntities for SkinnedMesh { - fn map_entities(&mut self, entity_map: &EntityMap) -> Result<(), MapEntitiesError> { + fn map_entities(&mut self, entity_mapper: &mut EntityMapper) { for joint in &mut self.joints { - *joint = entity_map.get(*joint)?; + *joint = entity_mapper.get_or_reserve(*joint); } - - Ok(()) } } diff --git a/crates/bevy_render/src/render_resource/pipeline_cache.rs b/crates/bevy_render/src/render_resource/pipeline_cache.rs index 783d35abb2..f958ee443b 100644 --- a/crates/bevy_render/src/render_resource/pipeline_cache.rs +++ b/crates/bevy_render/src/render_resource/pipeline_cache.rs @@ -752,6 +752,7 @@ fn log_shader_error(source: &ProcessedShader, error: &AsModuleDescriptorError) { let msg = error.emit_to_string(source); error!("failed to process shader:\n{}", msg); } + #[cfg(feature = "shader_format_glsl")] ShaderReflectError::GlslParse(errors) => { let source = source .get_glsl_source() @@ -776,6 +777,7 @@ fn log_shader_error(source: &ProcessedShader, error: &AsModuleDescriptorError) { error!("failed to process shader: \n{}", msg); } + #[cfg(feature = "shader_format_spirv")] ShaderReflectError::SpirVParse(error) => { error!("failed to process shader:\n{}", error); } @@ -818,9 +820,11 @@ fn log_shader_error(source: &ProcessedShader, error: &AsModuleDescriptorError) { error!("failed to process shader: \n{}", msg); } }, + #[cfg(feature = "shader_format_glsl")] AsModuleDescriptorError::WgslConversion(error) => { error!("failed to convert shader to wgsl: \n{}", error); } + #[cfg(feature = "shader_format_spirv")] AsModuleDescriptorError::SpirVConversion(error) => { error!("failed to convert shader to spirv: \n{}", error); } diff --git a/crates/bevy_render/src/render_resource/shader.rs b/crates/bevy_render/src/render_resource/shader.rs index a0b874c092..572e5fea4a 100644 --- a/crates/bevy_render/src/render_resource/shader.rs +++ b/crates/bevy_render/src/render_resource/shader.rs @@ -3,12 +3,16 @@ use crate::define_atomic_id; use bevy_asset::{AssetLoader, AssetPath, Handle, LoadContext, LoadedAsset}; use bevy_reflect::TypeUuid; use bevy_utils::{tracing::error, BoxedFuture, HashMap}; -use naga::{back::wgsl::WriterFlags, valid::Capabilities, valid::ModuleInfo, Module}; +#[cfg(feature = "shader_format_glsl")] +use naga::back::wgsl::WriterFlags; +use naga::{valid::Capabilities, valid::ModuleInfo, Module}; use once_cell::sync::Lazy; use regex::Regex; use std::{borrow::Cow, marker::Copy, ops::Deref, path::PathBuf, str::FromStr}; use thiserror::Error; -use wgpu::{util::make_spirv, Features, ShaderModuleDescriptor, ShaderSource}; +#[cfg(feature = "shader_format_spirv")] +use wgpu::util::make_spirv; +use wgpu::{Features, ShaderModuleDescriptor, ShaderSource}; define_atomic_id!(ShaderId); @@ -16,8 +20,10 @@ define_atomic_id!(ShaderId); pub enum ShaderReflectError { #[error(transparent)] WgslParse(#[from] naga::front::wgsl::ParseError), + #[cfg(feature = "shader_format_glsl")] #[error("GLSL Parse Error: {0:?}")] GlslParse(Vec), + #[cfg(feature = "shader_format_spirv")] #[error(transparent)] SpirVParse(#[from] naga::front::spv::Error), #[error(transparent)] @@ -120,12 +126,18 @@ impl ProcessedShader { let module = match &self { // TODO: process macros here ProcessedShader::Wgsl(source) => naga::front::wgsl::parse_str(source)?, + #[cfg(feature = "shader_format_glsl")] ProcessedShader::Glsl(source, shader_stage) => { - let mut parser = naga::front::glsl::Parser::default(); + let mut parser = naga::front::glsl::Frontend::default(); parser .parse(&naga::front::glsl::Options::from(*shader_stage), source) .map_err(ShaderReflectError::GlslParse)? } + #[cfg(not(feature = "shader_format_glsl"))] + ProcessedShader::Glsl(_source, _shader_stage) => { + unimplemented!("Enable feature \"shader_format_glsl\" to use GLSL shaders") + } + #[cfg(feature = "shader_format_spirv")] ProcessedShader::SpirV(source) => naga::front::spv::parse_u8_slice( source, &naga::front::spv::Options { @@ -133,10 +145,14 @@ impl ProcessedShader { ..naga::front::spv::Options::default() }, )?, + #[cfg(not(feature = "shader_format_spirv"))] + ProcessedShader::SpirV(_source) => { + unimplemented!("Enable feature \"shader_format_spirv\" to use SPIR-V shaders") + } }; const CAPABILITIES: &[(Features, Capabilities)] = &[ (Features::PUSH_CONSTANTS, Capabilities::PUSH_CONSTANT), - (Features::SHADER_FLOAT64, Capabilities::FLOAT64), + (Features::SHADER_F64, Capabilities::FLOAT64), ( Features::SHADER_PRIMITIVE_INDEX, Capabilities::PRIMITIVE_INDEX, @@ -172,7 +188,7 @@ impl ProcessedShader { pub fn get_module_descriptor( &self, - features: Features, + _features: Features, ) -> Result { Ok(ShaderModuleDescriptor { label: None, @@ -182,18 +198,28 @@ impl ProcessedShader { // Parse and validate the shader early, so that (e.g. while hot reloading) we can // display nicely formatted error messages instead of relying on just displaying the error string // returned by wgpu upon creating the shader module. - let _ = self.reflect(features)?; + let _ = self.reflect(_features)?; ShaderSource::Wgsl(source.clone()) } + #[cfg(feature = "shader_format_glsl")] ProcessedShader::Glsl(_source, _stage) => { - let reflection = self.reflect(features)?; + let reflection = self.reflect(_features)?; // TODO: it probably makes more sense to convert this to spirv, but as of writing // this comment, naga's spirv conversion is broken let wgsl = reflection.get_wgsl()?; ShaderSource::Wgsl(wgsl.into()) } + #[cfg(not(feature = "shader_format_glsl"))] + ProcessedShader::Glsl(_source, _stage) => { + unimplemented!("Enable feature \"shader_format_glsl\" to use GLSL shaders") + } + #[cfg(feature = "shader_format_spirv")] ProcessedShader::SpirV(source) => make_spirv(source), + #[cfg(not(feature = "shader_format_spirv"))] + ProcessedShader::SpirV(_source) => { + unimplemented!() + } }, }) } @@ -203,8 +229,10 @@ impl ProcessedShader { pub enum AsModuleDescriptorError { #[error(transparent)] ShaderReflectError(#[from] ShaderReflectError), + #[cfg(feature = "shader_format_glsl")] #[error(transparent)] WgslConversion(#[from] naga::back::wgsl::Error), + #[cfg(feature = "shader_format_spirv")] #[error(transparent)] SpirVConversion(#[from] naga::back::spv::Error), } @@ -215,6 +243,7 @@ pub struct ShaderReflection { } impl ShaderReflection { + #[cfg(feature = "shader_format_spirv")] pub fn get_spirv(&self) -> Result, naga::back::spv::Error> { naga::back::spv::write_vec( &self.module, @@ -227,6 +256,7 @@ impl ShaderReflection { ) } + #[cfg(feature = "shader_format_glsl")] pub fn get_wgsl(&self) -> Result { naga::back::wgsl::write_string(&self.module, &self.module_info, WriterFlags::EXPLICIT_TYPES) } diff --git a/crates/bevy_render/src/texture/dds.rs b/crates/bevy_render/src/texture/dds.rs index bd008a3600..bfedd4b102 100644 --- a/crates/bevy_render/src/texture/dds.rs +++ b/crates/bevy_render/src/texture/dds.rs @@ -215,7 +215,7 @@ pub fn dds_format_to_texture_format( } DxgiFormat::BC6H_Typeless | DxgiFormat::BC6H_UF16 => TextureFormat::Bc6hRgbUfloat, - DxgiFormat::BC6H_SF16 => TextureFormat::Bc6hRgbSfloat, + DxgiFormat::BC6H_SF16 => TextureFormat::Bc6hRgbFloat, DxgiFormat::BC7_Typeless | DxgiFormat::BC7_UNorm | DxgiFormat::BC7_UNorm_sRGB => { if is_srgb { TextureFormat::Bc7RgbaUnormSrgb diff --git a/crates/bevy_render/src/texture/fallback_image.rs b/crates/bevy_render/src/texture/fallback_image.rs index c262eba364..59688492bc 100644 --- a/crates/bevy_render/src/texture/fallback_image.rs +++ b/crates/bevy_render/src/texture/fallback_image.rs @@ -1,5 +1,3 @@ -use std::num::NonZeroU32; - use crate::{render_resource::*, texture::DefaultImageSampler}; use bevy_derive::{Deref, DerefMut}; use bevy_ecs::{ @@ -64,7 +62,7 @@ fn fallback_image_new( image.texture_descriptor.usage |= TextureUsages::RENDER_ATTACHMENT; // We can't create textures with data when it's a depth texture or when using multiple samples - let texture = if format.describe().sample_type == TextureSampleType::Depth || samples > 1 { + let texture = if format.is_depth_stencil_format() || samples > 1 { render_device.create_texture(&image.texture_descriptor) } else { render_device.create_texture_with_data(render_queue, &image.texture_descriptor, &image.data) @@ -72,7 +70,7 @@ fn fallback_image_new( let texture_view = texture.create_view(&TextureViewDescriptor { dimension: Some(dimension), - array_layer_count: NonZeroU32::new(extents.depth_or_array_layers), + array_layer_count: Some(extents.depth_or_array_layers), ..TextureViewDescriptor::default() }); let sampler = match image.sampler_descriptor { diff --git a/crates/bevy_render/src/texture/image.rs b/crates/bevy_render/src/texture/image.rs index a2424365fe..bf3396e327 100644 --- a/crates/bevy_render/src/texture/image.rs +++ b/crates/bevy_render/src/texture/image.rs @@ -386,15 +386,15 @@ impl Image { /// Whether the texture format is compressed or uncompressed pub fn is_compressed(&self) -> bool { - let format_description = self.texture_descriptor.format.describe(); + let format_description = self.texture_descriptor.format; format_description - .required_features - .contains(wgpu::Features::TEXTURE_COMPRESSION_ASTC_LDR) + .required_features() + .contains(wgpu::Features::TEXTURE_COMPRESSION_ASTC) || format_description - .required_features + .required_features() .contains(wgpu::Features::TEXTURE_COMPRESSION_BC) || format_description - .required_features + .required_features() .contains(wgpu::Features::TEXTURE_COMPRESSION_ETC2) } } @@ -482,9 +482,9 @@ pub trait TextureFormatPixelInfo { impl TextureFormatPixelInfo for TextureFormat { fn pixel_size(&self) -> usize { - let info = self.describe(); - match info.block_dimensions { - (1, 1) => info.block_size.into(), + let info = self; + match info.block_dimensions() { + (1, 1) => info.block_size(None).unwrap() as usize, _ => panic!("Using pixel_size for compressed textures is invalid"), } } @@ -568,7 +568,7 @@ bitflags::bitflags! { impl CompressedImageFormats { pub fn from_features(features: wgpu::Features) -> Self { let mut supported_compressed_formats = Self::default(); - if features.contains(wgpu::Features::TEXTURE_COMPRESSION_ASTC_LDR) { + if features.contains(wgpu::Features::TEXTURE_COMPRESSION_ASTC) { supported_compressed_formats |= Self::ASTC_LDR; } if features.contains(wgpu::Features::TEXTURE_COMPRESSION_BC) { @@ -593,7 +593,7 @@ impl CompressedImageFormats { | TextureFormat::Bc5RgUnorm | TextureFormat::Bc5RgSnorm | TextureFormat::Bc6hRgbUfloat - | TextureFormat::Bc6hRgbSfloat + | TextureFormat::Bc6hRgbFloat | TextureFormat::Bc7RgbaUnorm | TextureFormat::Bc7RgbaUnormSrgb => self.contains(CompressedImageFormats::BC), TextureFormat::Etc2Rgb8Unorm diff --git a/crates/bevy_render/src/texture/ktx2.rs b/crates/bevy_render/src/texture/ktx2.rs index 6202df95d5..41801178de 100644 --- a/crates/bevy_render/src/texture/ktx2.rs +++ b/crates/bevy_render/src/texture/ktx2.rs @@ -154,12 +154,13 @@ pub fn ktx2_buffer_to_image( TranscodeFormat::Uastc(data_format) => { let (transcode_block_format, texture_format) = get_transcoded_formats(supported_compressed_formats, data_format, is_srgb); - let texture_format_info = texture_format.describe(); + let texture_format_info = texture_format; let (block_width_pixels, block_height_pixels) = ( - texture_format_info.block_dimensions.0 as u32, - texture_format_info.block_dimensions.1 as u32, + texture_format_info.block_dimensions().0, + texture_format_info.block_dimensions().1, ); - let block_bytes = texture_format_info.block_size as u32; + // Texture is not a depth or stencil format, it is possible to pass `None` and unwrap + let block_bytes = texture_format_info.block_size(None).unwrap(); let transcoder = LowLevelUastcTranscoder::new(); for (level, level_data) in levels.iter().enumerate() { @@ -233,12 +234,13 @@ pub fn ktx2_buffer_to_image( } // Reorder data from KTX2 MipXLayerYFaceZ to wgpu LayerYFaceZMipX - let texture_format_info = texture_format.describe(); + let texture_format_info = texture_format; let (block_width_pixels, block_height_pixels) = ( - texture_format_info.block_dimensions.0 as usize, - texture_format_info.block_dimensions.1 as usize, + texture_format_info.block_dimensions().0 as usize, + texture_format_info.block_dimensions().1 as usize, ); - let block_bytes = texture_format_info.block_size as usize; + // Texture is not a depth or stencil format, it is possible to pass `None` and unwrap + let block_bytes = texture_format_info.block_size(None).unwrap() as usize; let mut wgpu_data = vec![Vec::default(); (layer_count * face_count) as usize]; for (level, level_data) in levels.iter().enumerate() { @@ -1037,7 +1039,7 @@ pub fn ktx2_dfd_to_texture_format( if sample_information[0].lower == 0 { TextureFormat::Bc6hRgbUfloat } else { - TextureFormat::Bc6hRgbSfloat + TextureFormat::Bc6hRgbFloat } } Some(ColorModel::BC7) => { @@ -1311,7 +1313,7 @@ pub fn ktx2_format_to_texture_format( ktx2::Format::BC5_UNORM_BLOCK => TextureFormat::Bc5RgUnorm, ktx2::Format::BC5_SNORM_BLOCK => TextureFormat::Bc5RgSnorm, ktx2::Format::BC6H_UFLOAT_BLOCK => TextureFormat::Bc6hRgbUfloat, - ktx2::Format::BC6H_SFLOAT_BLOCK => TextureFormat::Bc6hRgbSfloat, + ktx2::Format::BC6H_SFLOAT_BLOCK => TextureFormat::Bc6hRgbFloat, ktx2::Format::BC7_UNORM_BLOCK | ktx2::Format::BC7_SRGB_BLOCK => { if is_srgb { TextureFormat::Bc7RgbaUnormSrgb diff --git a/crates/bevy_render/src/view/window/screenshot.rs b/crates/bevy_render/src/view/window/screenshot.rs index 2a67f43005..b573c5269f 100644 --- a/crates/bevy_render/src/view/window/screenshot.rs +++ b/crates/bevy_render/src/view/window/screenshot.rs @@ -1,4 +1,4 @@ -use std::{borrow::Cow, num::NonZeroU32, path::Path}; +use std::{borrow::Cow, path::Path}; use bevy_app::Plugin; use bevy_asset::{load_internal_asset, HandleUntyped}; @@ -71,10 +71,47 @@ impl ScreenshotManager { // discard the alpha channel which stores brightness values when HDR is enabled to make sure // the screenshot looks right let img = dyn_img.to_rgb8(); + #[cfg(not(target_arch = "wasm32"))] match img.save_with_format(&path, format) { Ok(_) => info!("Screenshot saved to {}", path.display()), Err(e) => error!("Cannot save screenshot, IO error: {e}"), } + + #[cfg(target_arch = "wasm32")] + { + match (|| { + use image::EncodableLayout; + use wasm_bindgen::{JsCast, JsValue}; + + let mut image_buffer = std::io::Cursor::new(Vec::new()); + img.write_to(&mut image_buffer, format) + .map_err(|e| JsValue::from_str(&format!("{e}")))?; + // SAFETY: `image_buffer` only exist in this closure, and is not used after this line + let parts = js_sys::Array::of1(&unsafe { + js_sys::Uint8Array::view(image_buffer.into_inner().as_bytes()) + .into() + }); + let blob = web_sys::Blob::new_with_u8_array_sequence(&parts)?; + let url = web_sys::Url::create_object_url_with_blob(&blob)?; + let window = web_sys::window().unwrap(); + let document = window.document().unwrap(); + let link = document.create_element("a")?; + link.set_attribute("href", &url)?; + link.set_attribute( + "download", + path.file_name() + .and_then(|filename| filename.to_str()) + .ok_or_else(|| JsValue::from_str("Invalid filename"))?, + )?; + let html_element = link.dyn_into::()?; + html_element.click(); + web_sys::Url::revoke_object_url(&url)?; + Ok::<(), JsValue>(()) + })() { + Ok(_) => info!("Screenshot saved to {}", path.display()), + Err(e) => error!("Cannot save screenshot, error: {e:?}"), + }; + } } Err(e) => error!("Cannot save screenshot, requested format not recognized: {e}"), }, @@ -102,9 +139,37 @@ impl Plugin for ScreenshotPlugin { if let Ok(render_app) = app.get_sub_app_mut(RenderApp) { render_app.init_resource::>(); } + + #[cfg(feature = "bevy_ci_testing")] + if app + .world + .contains_resource::() + { + app.add_systems(bevy_app::Update, ci_testing_screenshot_at); + } } } +#[cfg(feature = "bevy_ci_testing")] +fn ci_testing_screenshot_at( + mut current_frame: bevy_ecs::prelude::Local, + ci_testing_config: bevy_ecs::prelude::Res, + mut screenshot_manager: ResMut, + main_window: Query>, +) { + if ci_testing_config + .screenshot_frames + .contains(&*current_frame) + { + info!("Taking a screenshot at frame {}.", *current_frame); + let path = format!("./screenshot-{}.png", *current_frame); + screenshot_manager + .save_screenshot_to_disk(main_window.single(), path) + .unwrap(); + } + *current_frame += 1; +} + pub(crate) fn align_byte_size(value: u32) -> u32 { value + (COPY_BYTES_PER_ROW_ALIGNMENT - (value % COPY_BYTES_PER_ROW_ALIGNMENT)) } @@ -117,7 +182,7 @@ pub(crate) fn layout_data(width: u32, height: u32, format: TextureFormat) -> Ima ImageDataLayout { bytes_per_row: if height > 1 { // 1 = 1 row - NonZeroU32::new(get_aligned_size(width, 1, format.pixel_size() as u32)) + Some(get_aligned_size(width, 1, format.pixel_size() as u32)) } else { None }, diff --git a/crates/bevy_scene/src/dynamic_scene.rs b/crates/bevy_scene/src/dynamic_scene.rs index 0a45e0e82b..4acf375d12 100644 --- a/crates/bevy_scene/src/dynamic_scene.rs +++ b/crates/bevy_scene/src/dynamic_scene.rs @@ -4,8 +4,7 @@ use crate::{DynamicSceneBuilder, Scene, SceneSpawnError}; use anyhow::Result; use bevy_app::AppTypeRegistry; use bevy_ecs::{ - entity::EntityMap, - prelude::Entity, + entity::{Entity, EntityMap}, reflect::{ReflectComponent, ReflectMapEntities}, world::World, }; @@ -36,8 +35,10 @@ pub struct DynamicScene { /// A reflection-powered serializable representation of an entity and its components. pub struct DynamicEntity { - /// The transiently unique identifier of a corresponding [`Entity`](bevy_ecs::entity::Entity). - pub entity: u32, + /// 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 [`Reflect`] trait. pub components: Vec>, @@ -101,7 +102,7 @@ impl DynamicScene { // or spawn a new entity with a transiently unique id if there is // no corresponding entry. let entity = *entity_map - .entry(bevy_ecs::entity::Entity::from_raw(scene_entity.entity)) + .entry(scene_entity.entity) .or_insert_with(|| world.spawn_empty().id()); let entity_mut = &mut world.entity_mut(entity); @@ -141,9 +142,7 @@ impl DynamicScene { "we should be getting TypeId from this TypeRegistration in the first place", ); if let Some(map_entities_reflect) = registration.data::() { - map_entities_reflect - .map_specific_entities(world, entity_map, &entities) - .unwrap(); + map_entities_reflect.map_entities(world, entity_map, &entities); } } diff --git a/crates/bevy_scene/src/dynamic_scene_builder.rs b/crates/bevy_scene/src/dynamic_scene_builder.rs index 0f25b1e484..b19d0a0993 100644 --- a/crates/bevy_scene/src/dynamic_scene_builder.rs +++ b/crates/bevy_scene/src/dynamic_scene_builder.rs @@ -38,7 +38,7 @@ use std::collections::BTreeMap; /// ``` pub struct DynamicSceneBuilder<'w> { extracted_resources: BTreeMap>, - extracted_scene: BTreeMap, + extracted_scene: BTreeMap, type_registry: AppTypeRegistry, original_world: &'w World, } @@ -123,19 +123,17 @@ impl<'w> DynamicSceneBuilder<'w> { let type_registry = self.type_registry.read(); for entity in entities { - let index = entity.index(); - - if self.extracted_scene.contains_key(&index) { + if self.extracted_scene.contains_key(&entity) { continue; } let mut entry = DynamicEntity { - entity: index, + entity, components: Vec::new(), }; - let entity = self.original_world.entity(entity); - for component_id in entity.archetype().components() { + 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 @@ -145,13 +143,13 @@ impl<'w> DynamicSceneBuilder<'w> { let component = type_registry .get(type_id)? .data::()? - .reflect(entity)?; + .reflect(original_entity)?; entry.components.push(component.clone_value()); Some(()) }; extract_and_push(); } - self.extracted_scene.insert(index, entry); + self.extracted_scene.insert(entity, entry); } drop(type_registry); @@ -243,7 +241,7 @@ mod tests { let scene = builder.build(); assert_eq!(scene.entities.len(), 1); - assert_eq!(scene.entities[0].entity, entity.index()); + assert_eq!(scene.entities[0].entity, entity); assert_eq!(scene.entities[0].components.len(), 1); assert!(scene.entities[0].components[0].represents::()); } @@ -264,7 +262,7 @@ mod tests { let scene = builder.build(); assert_eq!(scene.entities.len(), 1); - assert_eq!(scene.entities[0].entity, entity.index()); + assert_eq!(scene.entities[0].entity, entity); assert_eq!(scene.entities[0].components.len(), 1); assert!(scene.entities[0].components[0].represents::()); } @@ -288,7 +286,7 @@ mod tests { let scene = builder.build(); assert_eq!(scene.entities.len(), 1); - assert_eq!(scene.entities[0].entity, entity.index()); + assert_eq!(scene.entities[0].entity, entity); assert_eq!(scene.entities[0].components.len(), 2); assert!(scene.entities[0].components[0].represents::()); assert!(scene.entities[0].components[1].represents::()); @@ -315,10 +313,10 @@ mod tests { let mut entities = builder.build().entities.into_iter(); // Assert entities are ordered - assert_eq!(entity_a.index(), entities.next().map(|e| e.entity).unwrap()); - assert_eq!(entity_b.index(), entities.next().map(|e| e.entity).unwrap()); - assert_eq!(entity_c.index(), entities.next().map(|e| e.entity).unwrap()); - assert_eq!(entity_d.index(), entities.next().map(|e| e.entity).unwrap()); + 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] @@ -345,7 +343,7 @@ mod tests { 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.index(), entity_a.index()]); + assert_eq!(scene_entities, [entity_a_b, entity_a]); } #[test] @@ -365,7 +363,7 @@ mod tests { let scene = builder.build(); assert_eq!(scene.entities.len(), 1); - assert_eq!(scene.entities[0].entity, entity_a.index()); + assert_eq!(scene.entities[0].entity, entity_a); } #[test] diff --git a/crates/bevy_scene/src/scene.rs b/crates/bevy_scene/src/scene.rs index 462c1ec41e..741f570314 100644 --- a/crates/bevy_scene/src/scene.rs +++ b/crates/bevy_scene/src/scene.rs @@ -120,9 +120,7 @@ impl Scene { for registration in type_registry.iter() { if let Some(map_entities_reflect) = registration.data::() { - map_entities_reflect - .map_entities(world, &instance_info.entity_map) - .unwrap(); + map_entities_reflect.map_all_entities(world, &mut instance_info.entity_map); } } diff --git a/crates/bevy_scene/src/scene_spawner.rs b/crates/bevy_scene/src/scene_spawner.rs index 5a46aa14e2..94abfccc39 100644 --- a/crates/bevy_scene/src/scene_spawner.rs +++ b/crates/bevy_scene/src/scene_spawner.rs @@ -8,7 +8,7 @@ use bevy_ecs::{ world::{Mut, World}, }; use bevy_hierarchy::{AddChild, Parent}; -use bevy_utils::{tracing::error, HashMap}; +use bevy_utils::{tracing::error, HashMap, HashSet}; use thiserror::Error; use uuid::Uuid; @@ -316,6 +316,26 @@ impl SceneSpawner { pub fn scene_spawner_system(world: &mut World) { world.resource_scope(|world, mut scene_spawner: Mut| { + // 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_some(); + + 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::>>(); let mut updated_spawned_scenes = Vec::new(); diff --git a/crates/bevy_scene/src/serde.rs b/crates/bevy_scene/src/serde.rs index debe7c5451..635feee649 100644 --- a/crates/bevy_scene/src/serde.rs +++ b/crates/bevy_scene/src/serde.rs @@ -1,5 +1,6 @@ use crate::{DynamicEntity, DynamicScene}; use anyhow::Result; +use bevy_ecs::entity::Entity; use bevy_reflect::serde::{TypedReflectDeserializer, TypedReflectSerializer}; use bevy_reflect::{ serde::{TypeRegistrationDeserializer, UntypedReflectDeserializer}, @@ -260,9 +261,9 @@ impl<'a, 'de> Visitor<'de> for SceneEntitiesVisitor<'a> { A: MapAccess<'de>, { let mut entities = Vec::new(); - while let Some(id) = map.next_key::()? { + while let Some(entity) = map.next_key::()? { let entity = map.next_value_seed(SceneEntityDeserializer { - id, + entity, type_registry: self.type_registry, })?; entities.push(entity); @@ -273,7 +274,7 @@ impl<'a, 'de> Visitor<'de> for SceneEntitiesVisitor<'a> { } pub struct SceneEntityDeserializer<'a> { - pub id: u32, + pub entity: Entity, pub type_registry: &'a TypeRegistry, } @@ -288,7 +289,7 @@ impl<'a, 'de> DeserializeSeed<'de> for SceneEntityDeserializer<'a> { ENTITY_STRUCT, &[ENTITY_FIELD_COMPONENTS], SceneEntityVisitor { - id: self.id, + entity: self.entity, registry: self.type_registry, }, ) @@ -296,7 +297,7 @@ impl<'a, 'de> DeserializeSeed<'de> for SceneEntityDeserializer<'a> { } struct SceneEntityVisitor<'a> { - pub id: u32, + pub entity: Entity, pub registry: &'a TypeRegistry, } @@ -318,7 +319,7 @@ impl<'a, 'de> Visitor<'de> for SceneEntityVisitor<'a> { .ok_or_else(|| Error::missing_field(ENTITY_FIELD_COMPONENTS))?; Ok(DynamicEntity { - entity: self.id, + entity: self.entity, components, }) } @@ -346,7 +347,7 @@ impl<'a, 'de> Visitor<'de> for SceneEntityVisitor<'a> { .take() .ok_or_else(|| Error::missing_field(ENTITY_FIELD_COMPONENTS))?; Ok(DynamicEntity { - entity: self.id, + entity: self.entity, components, }) } @@ -424,8 +425,11 @@ mod tests { use crate::serde::{SceneDeserializer, SceneSerializer}; use crate::{DynamicScene, DynamicSceneBuilder}; use bevy_app::AppTypeRegistry; - use bevy_ecs::entity::EntityMap; + use bevy_ecs::entity::{Entity, EntityMap, EntityMapper, MapEntities}; use bevy_ecs::prelude::{Component, ReflectComponent, ReflectResource, Resource, World}; + use bevy_ecs::query::{With, Without}; + use bevy_ecs::reflect::ReflectMapEntities; + use bevy_ecs::world::FromWorld; use bevy_reflect::{FromReflect, Reflect, ReflectSerialize}; use bincode::Options; use serde::de::DeserializeSeed; @@ -466,6 +470,22 @@ mod tests { foo: i32, } + #[derive(Clone, Component, Reflect, PartialEq)] + #[reflect(Component, MapEntities, PartialEq)] + struct MyEntityRef(Entity); + + impl MapEntities for MyEntityRef { + fn map_entities(&mut self, entity_mapper: &mut EntityMapper) { + self.0 = entity_mapper.get_or_reserve(self.0); + } + } + + impl FromWorld for MyEntityRef { + fn from_world(_world: &mut World) -> Self { + Self(Entity::PLACEHOLDER) + } + } + fn create_world() -> World { let mut world = World::new(); let registry = AppTypeRegistry::default(); @@ -480,6 +500,8 @@ mod tests { registry.register_type_data::(); registry.register::<[usize; 3]>(); registry.register::<(f32, f32)>(); + registry.register::(); + registry.register::(); registry.register::(); } world.insert_resource(registry); @@ -596,6 +618,57 @@ mod tests { assert_eq!(1, dst_world.query::<&Baz>().iter(&dst_world).count()); } + #[test] + fn should_roundtrip_with_later_generations_and_obsolete_references() { + let mut world = create_world(); + + world.spawn_empty().despawn(); + + let a = world.spawn_empty().id(); + let foo = world.spawn(MyEntityRef(a)).insert(Foo(123)).id(); + world.despawn(a); + world.spawn(MyEntityRef(foo)).insert(Bar(123)); + + let registry = world.resource::(); + + let scene = DynamicScene::from_world(&world, registry); + + let serialized = scene + .serialize_ron(&world.resource::().0) + .unwrap(); + let mut deserializer = ron::de::Deserializer::from_str(&serialized).unwrap(); + let scene_deserializer = SceneDeserializer { + type_registry: ®istry.0.read(), + }; + + let deserialized_scene = scene_deserializer.deserialize(&mut deserializer).unwrap(); + + let mut map = EntityMap::default(); + let mut dst_world = create_world(); + deserialized_scene + .write_to_world(&mut dst_world, &mut map) + .unwrap(); + + assert_eq!(2, deserialized_scene.entities.len()); + assert_scene_eq(&scene, &deserialized_scene); + + let bar_to_foo = dst_world + .query_filtered::<&MyEntityRef, Without>() + .get_single(&dst_world) + .cloned() + .unwrap(); + let foo = dst_world + .query_filtered::>() + .get_single(&dst_world) + .unwrap(); + + assert_eq!(foo, bar_to_foo.0); + assert!(dst_world + .query_filtered::<&MyEntityRef, With>() + .iter(&dst_world) + .all(|r| world.get_entity(r.0).is_none())); + } + #[test] fn should_roundtrip_postcard() { let mut world = create_world(); @@ -696,12 +769,12 @@ mod tests { assert_eq!( vec![ - 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, - 37, 0, 0, 0, 0, 0, 0, 0, 98, 101, 118, 121, 95, 115, 99, 101, 110, 101, 58, 58, - 115, 101, 114, 100, 101, 58, 58, 116, 101, 115, 116, 115, 58, 58, 77, 121, 67, 111, - 109, 112, 111, 110, 101, 110, 116, 1, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, - 3, 0, 0, 0, 0, 0, 0, 0, 102, 102, 166, 63, 205, 204, 108, 64, 1, 0, 0, 0, 12, 0, 0, - 0, 0, 0, 0, 0, 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33 + 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, + 0, 0, 0, 0, 37, 0, 0, 0, 0, 0, 0, 0, 98, 101, 118, 121, 95, 115, 99, 101, 110, 101, + 58, 58, 115, 101, 114, 100, 101, 58, 58, 116, 101, 115, 116, 115, 58, 58, 77, 121, + 67, 111, 109, 112, 111, 110, 101, 110, 116, 1, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, 0, + 0, 0, 0, 3, 0, 0, 0, 0, 0, 0, 0, 102, 102, 166, 63, 205, 204, 108, 64, 1, 0, 0, 0, + 12, 0, 0, 0, 0, 0, 0, 0, 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33 ], serialized_scene ); @@ -732,7 +805,7 @@ mod tests { .entities .iter() .find(|dynamic_entity| dynamic_entity.entity == expected.entity) - .unwrap_or_else(|| panic!("missing entity (expected: `{}`)", expected.entity)); + .unwrap_or_else(|| panic!("missing entity (expected: `{:?}`)", expected.entity)); assert_eq!(expected.entity, received.entity, "entities did not match",); diff --git a/crates/bevy_sprite/src/mesh2d/mesh.rs b/crates/bevy_sprite/src/mesh2d/mesh.rs index d7b63d7d4c..11d4552af5 100644 --- a/crates/bevy_sprite/src/mesh2d/mesh.rs +++ b/crates/bevy_sprite/src/mesh2d/mesh.rs @@ -232,12 +232,7 @@ impl FromWorld for Mesh2dPipeline { &image.data, ImageDataLayout { offset: 0, - bytes_per_row: Some( - std::num::NonZeroU32::new( - image.texture_descriptor.size.width * format_size as u32, - ) - .unwrap(), - ), + bytes_per_row: Some(image.texture_descriptor.size.width * format_size as u32), rows_per_image: None, }, image.texture_descriptor.size, diff --git a/crates/bevy_sprite/src/render/mod.rs b/crates/bevy_sprite/src/render/mod.rs index 1279de05f0..725aab3234 100644 --- a/crates/bevy_sprite/src/render/mod.rs +++ b/crates/bevy_sprite/src/render/mod.rs @@ -109,12 +109,7 @@ impl FromWorld for SpritePipeline { &image.data, ImageDataLayout { offset: 0, - bytes_per_row: Some( - std::num::NonZeroU32::new( - image.texture_descriptor.size.width * format_size as u32, - ) - .unwrap(), - ), + bytes_per_row: Some(image.texture_descriptor.size.width * format_size as u32), rows_per_image: None, }, image.texture_descriptor.size, diff --git a/crates/bevy_text/src/font.rs b/crates/bevy_text/src/font.rs index 56fe1e60fc..4b80d74ffa 100644 --- a/crates/bevy_text/src/font.rs +++ b/crates/bevy_text/src/font.rs @@ -5,7 +5,7 @@ use bevy_render::{ texture::Image, }; -#[derive(Debug, TypeUuid)] +#[derive(Debug, TypeUuid, Clone)] #[uuid = "97059ac6-c9ba-4da9-95b6-bed82c3ce198"] pub struct Font { pub font: FontArc, diff --git a/crates/bevy_text/src/lib.rs b/crates/bevy_text/src/lib.rs index 4f9ab2497d..ed7825adbb 100644 --- a/crates/bevy_text/src/lib.rs +++ b/crates/bevy_text/src/lib.rs @@ -76,6 +76,7 @@ pub const DEFAULT_FONT_HANDLE: HandleUntyped = impl Plugin for TextPlugin { fn build(&self, app: &mut App) { app.add_asset::() + .add_debug_asset::() .add_asset::() .register_type::() .register_type::() diff --git a/crates/bevy_time/Cargo.toml b/crates/bevy_time/Cargo.toml index 3a7219ae04..7d4743b032 100644 --- a/crates/bevy_time/Cargo.toml +++ b/crates/bevy_time/Cargo.toml @@ -11,6 +11,7 @@ keywords = ["bevy"] [features] default = [] serialize = ["serde"] +bevy_ci_testing = ["bevy_app/bevy_ci_testing"] [dependencies] # bevy diff --git a/crates/bevy_time/src/lib.rs b/crates/bevy_time/src/lib.rs index ee923f7b96..704d54d860 100644 --- a/crates/bevy_time/src/lib.rs +++ b/crates/bevy_time/src/lib.rs @@ -47,6 +47,19 @@ impl Plugin for TimePlugin { .init_resource::() .add_systems(First, time_system.in_set(TimeSystem)) .add_systems(RunFixedUpdateLoop, run_fixed_update_schedule); + + #[cfg(feature = "bevy_ci_testing")] + if let Some(ci_testing_config) = app + .world + .get_resource::() + { + if let Some(frame_time) = ci_testing_config.frame_time { + app.world + .insert_resource(TimeUpdateStrategy::ManualDuration(Duration::from_secs_f32( + frame_time, + ))); + } + } } } @@ -60,7 +73,7 @@ pub enum TimeUpdateStrategy { Automatic, // Update [`Time`] with an exact `Instant` value ManualInstant(Instant), - // Update [`Time`] with the current time + a specified `Duration` + // Update [`Time`] with the last update time + a specified `Duration` ManualDuration(Duration), } @@ -107,7 +120,8 @@ fn time_system( TimeUpdateStrategy::Automatic => time.update_with_instant(new_time), TimeUpdateStrategy::ManualInstant(instant) => time.update_with_instant(*instant), TimeUpdateStrategy::ManualDuration(duration) => { - time.update_with_instant(Instant::now() + *duration); + let last_update = time.last_update().unwrap_or_else(|| time.startup()); + time.update_with_instant(last_update + *duration); } } } diff --git a/crates/bevy_ui/src/camera_config.rs b/crates/bevy_ui/src/camera_config.rs index 7be35d3fd7..69ce17705a 100644 --- a/crates/bevy_ui/src/camera_config.rs +++ b/crates/bevy_ui/src/camera_config.rs @@ -2,6 +2,8 @@ use bevy_ecs::component::Component; use bevy_ecs::prelude::With; +use bevy_ecs::reflect::ReflectComponent; +use bevy_reflect::{std_traits::ReflectDefault, FromReflect, Reflect, ReflectFromReflect}; use bevy_render::camera::Camera; use bevy_render::extract_component::ExtractComponent; @@ -10,8 +12,9 @@ use bevy_render::extract_component::ExtractComponent; /// When a [`Camera`] doesn't have the [`UiCameraConfig`] component, /// it will display the UI by default. /// -#[derive(Component, Clone, ExtractComponent)] +#[derive(Component, Clone, ExtractComponent, Reflect, FromReflect)] #[extract_component_filter(With)] +#[reflect(Component, FromReflect, Default)] pub struct UiCameraConfig { /// Whether to output UI to this camera view. /// diff --git a/crates/bevy_ui/src/focus.rs b/crates/bevy_ui/src/focus.rs index 71c3a6c8fa..c73ff172b3 100644 --- a/crates/bevy_ui/src/focus.rs +++ b/crates/bevy_ui/src/focus.rs @@ -10,7 +10,9 @@ use bevy_ecs::{ }; use bevy_input::{mouse::MouseButton, touch::Touches, Input}; use bevy_math::Vec2; -use bevy_reflect::{Reflect, ReflectDeserialize, ReflectSerialize}; +use bevy_reflect::{ + FromReflect, Reflect, ReflectDeserialize, ReflectFromReflect, ReflectSerialize, +}; use bevy_render::{camera::NormalizedRenderTarget, prelude::Camera, view::ComputedVisibility}; use bevy_transform::components::GlobalTransform; @@ -32,8 +34,10 @@ use smallvec::SmallVec; /// /// Note that you can also control the visibility of a node using the [`Display`](crate::ui_node::Display) property, /// which fully collapses it during layout calculations. -#[derive(Component, Copy, Clone, Eq, PartialEq, Debug, Reflect, Serialize, Deserialize)] -#[reflect(Component, Serialize, Deserialize, PartialEq)] +#[derive( + Component, Copy, Clone, Eq, PartialEq, Debug, Reflect, FromReflect, Serialize, Deserialize, +)] +#[reflect(Component, FromReflect, Serialize, Deserialize, PartialEq)] pub enum Interaction { /// The node has been clicked Clicked, @@ -68,10 +72,11 @@ impl Default for Interaction { PartialEq, Debug, Reflect, + FromReflect, Serialize, Deserialize, )] -#[reflect(Component, Serialize, Deserialize, PartialEq)] +#[reflect(Component, FromReflect, Serialize, Deserialize, PartialEq)] pub struct RelativeCursorPosition { /// Cursor position relative to size and position of the Node. pub normalized: Option, @@ -87,8 +92,10 @@ impl RelativeCursorPosition { } /// Describes whether the node should block interactions with lower nodes -#[derive(Component, Copy, Clone, Eq, PartialEq, Debug, Reflect, Serialize, Deserialize)] -#[reflect(Component, Serialize, Deserialize, PartialEq)] +#[derive( + Component, Copy, Clone, Eq, PartialEq, Debug, Reflect, FromReflect, Serialize, Deserialize, +)] +#[reflect(Component, FromReflect, Serialize, Deserialize, PartialEq)] pub enum FocusPolicy { /// Blocks interaction Block, diff --git a/crates/bevy_ui/src/geometry.rs b/crates/bevy_ui/src/geometry.rs index d58509ab53..363c8acf69 100644 --- a/crates/bevy_ui/src/geometry.rs +++ b/crates/bevy_ui/src/geometry.rs @@ -1,5 +1,5 @@ use crate::Val; -use bevy_reflect::Reflect; +use bevy_reflect::{FromReflect, Reflect, ReflectFromReflect}; use std::ops::{Div, DivAssign, Mul, MulAssign}; /// A type which is commonly used to define margins, paddings and borders. @@ -46,8 +46,8 @@ use std::ops::{Div, DivAssign, Mul, MulAssign}; /// bottom: Val::Px(40.0), /// }; /// ``` -#[derive(Copy, Clone, PartialEq, Debug, Reflect)] -#[reflect(PartialEq)] +#[derive(Copy, Clone, PartialEq, Debug, Reflect, FromReflect)] +#[reflect(FromReflect, PartialEq)] pub struct UiRect { /// The value corresponding to the left side of the UI rect. pub left: Val, @@ -285,8 +285,8 @@ impl Default for UiRect { /// A 2-dimensional area defined by a width and height. /// /// It is commonly used to define the size of a text or UI element. -#[derive(Copy, Clone, PartialEq, Debug, Reflect)] -#[reflect(PartialEq)] +#[derive(Copy, Clone, PartialEq, Debug, Reflect, FromReflect)] +#[reflect(FromReflect, PartialEq)] pub struct Size { /// The width of the 2-dimensional area. pub width: Val, diff --git a/crates/bevy_ui/src/layout/mod.rs b/crates/bevy_ui/src/layout/mod.rs index 8c808be06d..75dc688f8b 100644 --- a/crates/bevy_ui/src/layout/mod.rs +++ b/crates/bevy_ui/src/layout/mod.rs @@ -1,13 +1,14 @@ mod convert; -use crate::{CalculatedSize, Node, Style, UiScale}; +use crate::{ContentSize, Node, Style, UiScale}; use bevy_ecs::{ change_detection::DetectChanges, entity::Entity, event::EventReader, - query::{Changed, Or, With, Without}, + query::{Changed, With, Without}, removal_detection::RemovedComponents, system::{Query, Res, ResMut, Resource}, + world::Ref, }; use bevy_hierarchy::{Children, Parent}; use bevy_log::warn; @@ -16,11 +17,7 @@ use bevy_transform::components::Transform; use bevy_utils::HashMap; use bevy_window::{PrimaryWindow, Window, WindowResolution, WindowScaleFactorChanged}; use std::fmt; -use taffy::{ - prelude::{AvailableSpace, Size}, - style_helpers::TaffyMaxContent, - Taffy, -}; +use taffy::{prelude::Size, style_helpers::TaffyMaxContent, Taffy}; pub struct LayoutContext { pub scale_factor: f64, @@ -75,6 +72,8 @@ impl Default for UiSurface { } impl UiSurface { + /// Retrieves the taffy node corresponding to given entity exists, or inserts a new taffy node into the layout if no corresponding node exists. + /// Then convert the given `Style` and use it update the taffy node's style. pub fn upsert_node(&mut self, entity: Entity, style: &Style, context: &LayoutContext) { let mut added = false; let taffy = &mut self.taffy; @@ -90,43 +89,13 @@ impl UiSurface { } } - pub fn upsert_leaf( - &mut self, - entity: Entity, - style: &Style, - calculated_size: &CalculatedSize, - context: &LayoutContext, - ) { - let taffy = &mut self.taffy; - let taffy_style = convert::from_style(context, style); - let measure = calculated_size.measure.dyn_clone(); - let measure_func = taffy::node::MeasureFunc::Boxed(Box::new( - move |constraints: Size>, available: Size| { - let size = measure.measure( - constraints.width, - constraints.height, - available.width, - available.height, - ); - taffy::geometry::Size { - width: size.x, - height: size.y, - } - }, - )); - if let Some(taffy_node) = self.entity_to_taffy.get(&entity) { - self.taffy.set_style(*taffy_node, taffy_style).unwrap(); - self.taffy - .set_measure(*taffy_node, Some(measure_func)) - .unwrap(); - } else { - let taffy_node = taffy - .new_leaf_with_measure(taffy_style, measure_func) - .unwrap(); - self.entity_to_taffy.insert(entity, taffy_node); - } + /// Update the `MeasureFunc` of the taffy node corresponding to the given [`Entity`]. + pub fn update_measure(&mut self, entity: Entity, measure_func: taffy::node::MeasureFunc) { + let taffy_node = self.entity_to_taffy.get(&entity).unwrap(); + self.taffy.set_measure(*taffy_node, Some(measure_func)).ok(); } + /// Update the children of the taffy node corresponding to the given [`Entity`]. pub fn update_children(&mut self, entity: Entity, children: &Children) { let mut taffy_children = Vec::with_capacity(children.len()); for child in children { @@ -160,6 +129,7 @@ without UI components as a child of an entity with UI components, results may be } } + /// Retrieve or insert the root layout node and update its size to match the size of the window. pub fn update_window(&mut self, window: Entity, window_resolution: &WindowResolution) { let taffy = &mut self.taffy; let node = self @@ -185,6 +155,7 @@ without UI components as a child of an entity with UI components, results may be .unwrap(); } + /// Set the ui node entities without a [`Parent`] as children to the root node in the taffy layout. pub fn set_window_children( &mut self, parent_window: Entity, @@ -197,6 +168,7 @@ without UI components as a child of an entity with UI components, results may be self.taffy.set_children(*taffy_node, &child_nodes).unwrap(); } + /// Compute the layout for each window entity's corresponding root node in the layout. pub fn compute_window_layouts(&mut self) { for window_node in self.window_nodes.values() { self.taffy @@ -214,6 +186,8 @@ without UI components as a child of an entity with UI components, results may be } } + /// Get the layout geometry for the taffy node corresponding to the ui node [`Entity`]. + /// Does not compute the layout geometry, `compute_window_layouts` should be run before using this function. pub fn get_layout(&self, entity: Entity) -> Result<&taffy::layout::Layout, LayoutError> { if let Some(taffy_node) = self.entity_to_taffy.get(&entity) { self.taffy @@ -235,6 +209,7 @@ pub enum LayoutError { TaffyError(taffy::error::TaffyError), } +/// Updates the UI's layout tree, computes the new layout geometry and then updates the sizes and transforms of all the UI nodes. #[allow(clippy::too_many_arguments)] pub fn ui_layout_system( primary_window: Query<(Entity, &Window), With>, @@ -244,18 +219,11 @@ pub fn ui_layout_system( mut resize_events: EventReader, mut ui_surface: ResMut, root_node_query: Query, Without)>, - full_node_query: Query<(Entity, &Style, Option<&CalculatedSize>), With>, - changed_style_query: Query< - (Entity, &Style), - (With, Without, Changed