Merge branch 'main' into transmission
This commit is contained in:
commit
447a0a1c2e
4
.github/example-run/breakout.ron
vendored
4
.github/example-run/breakout.ron
vendored
@ -1,3 +1,5 @@
|
||||
(
|
||||
exit_after: Some(900)
|
||||
exit_after: Some(900),
|
||||
frame_time: Some(0.03),
|
||||
screenshot_frames: [200],
|
||||
)
|
||||
|
||||
4
.github/example-run/load_gltf.ron
vendored
4
.github/example-run/load_gltf.ron
vendored
@ -1,3 +1,5 @@
|
||||
(
|
||||
exit_after: Some(300)
|
||||
exit_after: Some(300),
|
||||
frame_time: Some(0.03),
|
||||
screenshot_frames: [100],
|
||||
)
|
||||
|
||||
10
.github/workflows/ci.yml
vendored
10
.github/workflows/ci.yml
vendored
@ -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: |
|
||||
|
||||
@ -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"
|
||||
|
||||
@ -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()));
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
@ -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() {
|
||||
|
||||
@ -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<u32>,
|
||||
/// The time in seconds to update for each frame.
|
||||
pub frame_time: Option<f32>,
|
||||
/// Frames at which to capture a screenshot.
|
||||
#[serde(default)]
|
||||
pub screenshot_frames: Vec<u32>,
|
||||
}
|
||||
|
||||
fn ci_testing_exit_after(
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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<I: Into<HandleId>, T: Asset>(&self, id: I) -> Handle<T> {
|
||||
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<I: Into<HandleId>>(&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<P: AsRef<Path>>(&self, path: P) -> Result<Vec<u8>, AssetIoError> {
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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()
|
||||
})
|
||||
}
|
||||
|
||||
@ -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]
|
||||
|
||||
@ -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<Entity, Entity>,
|
||||
@ -91,11 +77,8 @@ impl EntityMap {
|
||||
}
|
||||
|
||||
/// Returns the corresponding mapped entity.
|
||||
pub fn get(&self, entity: Entity) -> Result<Entity, MapEntitiesError> {
|
||||
self.map
|
||||
.get(&entity)
|
||||
.cloned()
|
||||
.ok_or(MapEntitiesError::EntityNotFound(entity))
|
||||
pub fn get(&self, entity: Entity) -> Option<Entity> {
|
||||
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<Item = (Entity, Entity)> + '_ {
|
||||
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<R>(
|
||||
&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());
|
||||
}
|
||||
}
|
||||
|
||||
@ -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<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
||||
where
|
||||
S: serde::Serializer,
|
||||
{
|
||||
serializer.serialize_u64(self.to_bits())
|
||||
}
|
||||
}
|
||||
|
||||
impl<'de> Deserialize<'de> for Entity {
|
||||
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
@ -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<C: Component + MapEntities> FromType<C> 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::<C>(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::<C>(entity) {
|
||||
component.map_entities(entity_map)?;
|
||||
map_all_entities: |world, entity_mapper| {
|
||||
let entities = entity_mapper.get_map().values().collect::<Vec<Entity>>();
|
||||
for entity in &entities {
|
||||
if let Some(mut component) = world.get_mut::<C>(*entity) {
|
||||
component.map_entities(entity_mapper);
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
@ -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.
|
||||
|
||||
@ -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<F>(&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<K, F>(&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<K, F>(&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<F>(&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<K, F>(&mut self, compare: F)
|
||||
where
|
||||
F: FnMut(&Entity) -> K,
|
||||
K: Ord,
|
||||
{
|
||||
self.0.sort_unstable_by_key(compare);
|
||||
}
|
||||
}
|
||||
|
||||
impl Deref for Children {
|
||||
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -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"]
|
||||
|
||||
@ -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,
|
||||
|
||||
@ -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;
|
||||
|
||||
|
||||
@ -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,
|
||||
|
||||
@ -192,8 +192,8 @@ pub(crate) fn impl_enum(reflect_enum: &ReflectEnum) -> TokenStream {
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn get_type_info(&self) -> &'static #bevy_reflect_path::TypeInfo {
|
||||
<Self as #bevy_reflect_path::Typed>::type_info()
|
||||
fn get_represented_type_info(&self) -> #FQOption<&'static #bevy_reflect_path::TypeInfo> {
|
||||
#FQOption::Some(<Self as #bevy_reflect_path::Typed>::type_info())
|
||||
}
|
||||
|
||||
#[inline]
|
||||
|
||||
@ -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 {
|
||||
<Self as #bevy_reflect_path::Typed>::type_info()
|
||||
fn get_represented_type_info(&self) -> #FQOption<&'static #bevy_reflect_path::TypeInfo> {
|
||||
#FQOption::Some(<Self as #bevy_reflect_path::Typed>::type_info())
|
||||
}
|
||||
|
||||
#[inline]
|
||||
|
||||
@ -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 {
|
||||
<Self as #bevy_reflect_path::Typed>::type_info()
|
||||
fn get_represented_type_info(&self) -> #FQOption<&'static #bevy_reflect_path::TypeInfo> {
|
||||
#FQOption::Some(<Self as #bevy_reflect_path::Typed>::type_info())
|
||||
}
|
||||
|
||||
#[inline]
|
||||
|
||||
@ -49,8 +49,8 @@ pub(crate) fn impl_value(meta: &ReflectMeta) -> TokenStream {
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn get_type_info(&self) -> &'static #bevy_reflect_path::TypeInfo {
|
||||
<Self as #bevy_reflect_path::Typed>::type_info()
|
||||
fn get_represented_type_info(&self) -> #FQOption<&'static #bevy_reflect_path::TypeInfo> {
|
||||
#FQOption::Some(<Self as #bevy_reflect_path::Typed>::type_info())
|
||||
}
|
||||
|
||||
#[inline]
|
||||
|
||||
@ -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<dyn Reflect>]>,
|
||||
}
|
||||
|
||||
@ -175,14 +172,14 @@ impl DynamicArray {
|
||||
#[inline]
|
||||
pub fn new(values: Box<[Box<dyn Reflect>]>) -> Self {
|
||||
Self {
|
||||
name: String::default(),
|
||||
represented_type: None,
|
||||
values,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn from_vec<T: Reflect>(values: Vec<T>) -> Self {
|
||||
Self {
|
||||
name: String::default(),
|
||||
represented_type: None,
|
||||
values: values
|
||||
.into_iter()
|
||||
.map(|field| Box::new(field) as Box<dyn Reflect>)
|
||||
@ -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::<Self>())
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn get_type_info(&self) -> &'static TypeInfo {
|
||||
<Self as Typed>::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<bool> {
|
||||
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::<Self>()))
|
||||
}
|
||||
}
|
||||
|
||||
/// An iterator over an [`Array`].
|
||||
pub struct ArrayIter<'a> {
|
||||
array: &'a dyn Array,
|
||||
|
||||
@ -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<I: Into<String>, V: Into<DynamicVariant>>(
|
||||
name: I,
|
||||
variant_name: I,
|
||||
variant: V,
|
||||
) -> Self {
|
||||
pub fn new<I: Into<String>, V: Into<DynamicVariant>>(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<I: Into<String>, V: Into<DynamicVariant>>(
|
||||
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<I: Into<String>, V: Into<DynamicVariant>>(
|
||||
&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<TEnum: Enum>(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 {
|
||||
<Self as Typed>::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::<Self>()))
|
||||
}
|
||||
}
|
||||
|
||||
@ -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::<TestEnum<f32>>(), "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::<TestEnum<f32>>(), "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::<TestEnum>(), "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::<TestEnum>(), "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::<TestEnum>(), "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::<TestEnum>(), "C", data);
|
||||
let dyn_enum = DynamicEnum::new("C", data);
|
||||
value.apply(&dyn_enum);
|
||||
assert_eq!(
|
||||
TestEnum::C {
|
||||
|
||||
@ -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::<Foo>();
|
||||
///
|
||||
/// let mut reflected = DynamicTupleStruct::default();
|
||||
/// reflected.set_name(std::any::type_name::<Foo>().to_string());
|
||||
/// reflected.set_represented_type(Some(<Foo as Typed>::type_info()));
|
||||
///
|
||||
/// let registration = registry.get_with_name(reflected.type_name()).unwrap();
|
||||
/// let rfr = registration.data::<ReflectFromReflect>().unwrap();
|
||||
|
||||
@ -82,8 +82,8 @@ where
|
||||
std::any::type_name::<Self>()
|
||||
}
|
||||
|
||||
fn get_type_info(&self) -> &'static TypeInfo {
|
||||
<Self as Typed>::type_info()
|
||||
fn get_represented_type_info(&self) -> Option<&'static TypeInfo> {
|
||||
Some(<Self as Typed>::type_info())
|
||||
}
|
||||
|
||||
fn into_any(self: Box<Self>) -> Box<dyn Any> {
|
||||
|
||||
@ -244,8 +244,8 @@ macro_rules! impl_reflect_for_veclike {
|
||||
std::any::type_name::<Self>()
|
||||
}
|
||||
|
||||
fn get_type_info(&self) -> &'static TypeInfo {
|
||||
<Self as Typed>::type_info()
|
||||
fn get_represented_type_info(&self) -> Option<&'static TypeInfo> {
|
||||
Some(<Self as Typed>::type_info())
|
||||
}
|
||||
|
||||
fn into_any(self: Box<Self>) -> Box<dyn Any> {
|
||||
@ -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::<Self>()
|
||||
}
|
||||
|
||||
fn get_type_info(&self) -> &'static TypeInfo {
|
||||
<Self as Typed>::type_info()
|
||||
fn get_represented_type_info(&self) -> Option<&'static TypeInfo> {
|
||||
Some(<Self as Typed>::type_info())
|
||||
}
|
||||
|
||||
fn into_any(self: Box<Self>) -> Box<dyn Any> {
|
||||
@ -595,8 +595,8 @@ impl<T: Reflect, const N: usize> Reflect for [T; N] {
|
||||
std::any::type_name::<Self>()
|
||||
}
|
||||
|
||||
fn get_type_info(&self) -> &'static TypeInfo {
|
||||
<Self as Typed>::type_info()
|
||||
fn get_represented_type_info(&self) -> Option<&'static TypeInfo> {
|
||||
Some(<Self as Typed>::type_info())
|
||||
}
|
||||
|
||||
#[inline]
|
||||
@ -800,8 +800,8 @@ impl<T: FromReflect> Reflect for Option<T> {
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn get_type_info(&self) -> &'static TypeInfo {
|
||||
<Self as Typed>::type_info()
|
||||
fn get_represented_type_info(&self) -> Option<&'static TypeInfo> {
|
||||
Some(<Self as Typed>::type_info())
|
||||
}
|
||||
|
||||
#[inline]
|
||||
@ -965,8 +965,8 @@ impl Reflect for Cow<'static, str> {
|
||||
std::any::type_name::<Self>()
|
||||
}
|
||||
|
||||
fn get_type_info(&self) -> &'static TypeInfo {
|
||||
<Self as Typed>::type_info()
|
||||
fn get_represented_type_info(&self) -> Option<&'static TypeInfo> {
|
||||
Some(<Self as Typed>::type_info())
|
||||
}
|
||||
|
||||
fn into_any(self: Box<Self>) -> Box<dyn Any> {
|
||||
@ -1073,8 +1073,8 @@ impl Reflect for &'static Path {
|
||||
std::any::type_name::<Self>()
|
||||
}
|
||||
|
||||
fn get_type_info(&self) -> &'static TypeInfo {
|
||||
<Self as Typed>::type_info()
|
||||
fn get_represented_type_info(&self) -> Option<&'static TypeInfo> {
|
||||
Some(<Self as Typed>::type_info())
|
||||
}
|
||||
|
||||
fn into_any(self: Box<Self>) -> Box<dyn Any> {
|
||||
|
||||
@ -169,7 +169,7 @@
|
||||
//! ```
|
||||
//! # use bevy_reflect::{DynamicEnum, Reflect};
|
||||
//! let mut value = Some(123_i32);
|
||||
//! let patch = DynamicEnum::new(std::any::type_name::<Option<i32>>(), "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::<i32>());
|
||||
|
||||
// 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::<MyStruct>());
|
||||
|
||||
// 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::<MyGenericStruct<String>>());
|
||||
|
||||
// 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::<MyTuple>());
|
||||
|
||||
// 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::<MyList>());
|
||||
|
||||
// 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::<MySmallVec>());
|
||||
}
|
||||
|
||||
@ -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::<MyArray>());
|
||||
|
||||
// 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::<MyMap>());
|
||||
|
||||
// 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::<MyValue>());
|
||||
}
|
||||
|
||||
// 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::<MyDynamic>());
|
||||
assert_eq!(std::any::type_name::<MyDynamic>(), info.type_name());
|
||||
} else {
|
||||
panic!("Expected `TypeInfo::Dynamic`");
|
||||
}
|
||||
|
||||
let value: &dyn Reflect = &DynamicList::default();
|
||||
let info = value.get_type_info();
|
||||
assert!(info.is::<MyDynamic>());
|
||||
#[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")]
|
||||
|
||||
@ -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<Box<dyn Reflect>>,
|
||||
}
|
||||
|
||||
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::<Self>())
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn get_type_info(&self) -> &'static TypeInfo {
|
||||
<Self as Typed>::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::<Self>()))
|
||||
}
|
||||
}
|
||||
|
||||
impl IntoIterator for DynamicList {
|
||||
type Item = Box<dyn Reflect>;
|
||||
type IntoIter = std::vec::IntoIter<Self::Item>;
|
||||
|
||||
@ -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<dyn Reflect>, Box<dyn Reflect>)>,
|
||||
indices: HashMap<u64, usize>,
|
||||
}
|
||||
|
||||
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::<Self>())
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn get_type_info(&self) -> &'static TypeInfo {
|
||||
<Self as Typed>::type_info()
|
||||
fn get_represented_type_info(&self) -> Option<&'static TypeInfo> {
|
||||
self.represented_type
|
||||
}
|
||||
|
||||
fn into_any(self: Box<Self>) -> Box<dyn Any> {
|
||||
@ -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::<Self>()))
|
||||
}
|
||||
}
|
||||
|
||||
/// An iterator over the key-value pairs of a [`Map`].
|
||||
pub struct MapIter<'a> {
|
||||
pub(crate) map: &'a dyn Map,
|
||||
|
||||
@ -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<dyn Any>`][std::any::Any].
|
||||
fn into_any(self: Box<Self>) -> Box<dyn Any>;
|
||||
@ -216,6 +223,22 @@ pub trait Reflect: Any + Send + Sync {
|
||||
fn serializable(&self) -> Option<Serializable> {
|
||||
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 {
|
||||
|
||||
@ -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}",
|
||||
)))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
@ -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<E: Error>(
|
||||
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,
|
||||
|
||||
@ -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<Box<dyn Reflect>>,
|
||||
field_names: Vec<Cow<'static, str>>,
|
||||
field_indices: HashMap<Cow<'static, str>, 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::<Self>())
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn get_type_info(&self) -> &'static TypeInfo {
|
||||
<Self as Typed>::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::<Self>()))
|
||||
}
|
||||
}
|
||||
|
||||
/// Compares a [`Struct`] with a [`Reflect`] value.
|
||||
///
|
||||
/// Returns true if and only if all of the following are true:
|
||||
|
||||
@ -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<Box<dyn Reflect>>,
|
||||
}
|
||||
|
||||
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<dyn Reflect>) {
|
||||
self.represented_type = None;
|
||||
self.fields.push(value);
|
||||
self.generate_name();
|
||||
}
|
||||
|
||||
/// Appends a typed element with value `value` to the tuple.
|
||||
pub fn insert<T: Reflect>(&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 {
|
||||
<Self as Typed>::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::<Self>()))
|
||||
#[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::<Self>()),
|
||||
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::<Self>()
|
||||
}
|
||||
|
||||
fn get_type_info(&self) -> &'static TypeInfo {
|
||||
<Self as Typed>::type_info()
|
||||
fn get_represented_type_info(&self) -> Option<&'static TypeInfo> {
|
||||
Some(<Self as Typed>::type_info())
|
||||
}
|
||||
|
||||
fn into_any(self: Box<Self>) -> Box<dyn Any> {
|
||||
|
||||
@ -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<Box<dyn Reflect>>,
|
||||
}
|
||||
|
||||
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::<Self>())
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn get_type_info(&self) -> &'static TypeInfo {
|
||||
<Self as Typed>::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::<Self>()))
|
||||
}
|
||||
}
|
||||
|
||||
/// Compares a [`TupleStruct`] with a [`Reflect`] value.
|
||||
///
|
||||
/// Returns true if and only if all of the following are true:
|
||||
|
||||
@ -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<Self>) -> Box<dyn Any> { 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<T: Reflect>() -> Self {
|
||||
Self {
|
||||
type_name: std::any::type_name::<T>(),
|
||||
type_id: TypeId::of::<T>(),
|
||||
#[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<T: Any>(&self) -> bool {
|
||||
TypeId::of::<T>() == self.type_id
|
||||
}
|
||||
|
||||
/// The docstring of this value, if any.
|
||||
#[cfg(feature = "documentation")]
|
||||
pub fn docs(&self) -> Option<&'static str> {
|
||||
self.docs
|
||||
}
|
||||
}
|
||||
|
||||
@ -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<Self>) -> Box<dyn Any> { 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<T: Reflect> Reflect for Foo<T> {
|
||||
/// # 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<Self>) -> Box<dyn Any> { todo!() }
|
||||
/// # fn as_any(&self) -> &dyn Any { todo!() }
|
||||
/// # fn as_any_mut(&mut self) -> &mut dyn Any { todo!() }
|
||||
|
||||
@ -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"
|
||||
|
||||
@ -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(())
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -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);
|
||||
}
|
||||
|
||||
@ -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<naga::front::glsl::Error>),
|
||||
#[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<ShaderModuleDescriptor, AsModuleDescriptorError> {
|
||||
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<Vec<u32>, 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<String, naga::back::wgsl::Error> {
|
||||
naga::back::wgsl::write_string(&self.module, &self.module_info, WriterFlags::EXPLICIT_TYPES)
|
||||
}
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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 {
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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::<web_sys::HtmlElement>()?;
|
||||
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::<SpecializedRenderPipelines<ScreenshotToScreenPipeline>>();
|
||||
}
|
||||
|
||||
#[cfg(feature = "bevy_ci_testing")]
|
||||
if app
|
||||
.world
|
||||
.contains_resource::<bevy_app::ci_testing::CiTestingConfig>()
|
||||
{
|
||||
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<u32>,
|
||||
ci_testing_config: bevy_ecs::prelude::Res<bevy_app::ci_testing::CiTestingConfig>,
|
||||
mut screenshot_manager: ResMut<ScreenshotManager>,
|
||||
main_window: Query<Entity, With<bevy_window::PrimaryWindow>>,
|
||||
) {
|
||||
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
|
||||
},
|
||||
|
||||
@ -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<Box<dyn Reflect>>,
|
||||
@ -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::<ReflectMapEntities>() {
|
||||
map_entities_reflect
|
||||
.map_specific_entities(world, entity_map, &entities)
|
||||
.unwrap();
|
||||
map_entities_reflect.map_entities(world, entity_map, &entities);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -38,7 +38,7 @@ use std::collections::BTreeMap;
|
||||
/// ```
|
||||
pub struct DynamicSceneBuilder<'w> {
|
||||
extracted_resources: BTreeMap<ComponentId, Box<dyn Reflect>>,
|
||||
extracted_scene: BTreeMap<u32, DynamicEntity>,
|
||||
extracted_scene: BTreeMap<Entity, DynamicEntity>,
|
||||
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::<ReflectComponent>()?
|
||||
.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::<ComponentA>());
|
||||
}
|
||||
@ -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::<ComponentA>());
|
||||
}
|
||||
@ -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::<ComponentA>());
|
||||
assert!(scene.entities[0].components[1].represents::<ComponentB>());
|
||||
@ -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]
|
||||
|
||||
@ -120,9 +120,7 @@ impl Scene {
|
||||
|
||||
for registration in type_registry.iter() {
|
||||
if let Some(map_entities_reflect) = registration.data::<ReflectMapEntities>() {
|
||||
map_entities_reflect
|
||||
.map_entities(world, &instance_info.entity_map)
|
||||
.unwrap();
|
||||
map_entities_reflect.map_all_entities(world, &mut instance_info.entity_map);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -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<SceneSpawner>| {
|
||||
// remove any loading instances where parent is deleted
|
||||
let mut dead_instances = HashSet::default();
|
||||
scene_spawner
|
||||
.scenes_with_parent
|
||||
.retain(|(instance, parent)| {
|
||||
let retain = world.get_entity(*parent).is_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::<Events<AssetEvent<DynamicScene>>>();
|
||||
|
||||
let mut updated_spawned_scenes = Vec::new();
|
||||
|
||||
@ -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::<u32>()? {
|
||||
while let Some(entity) = map.next_key::<Entity>()? {
|
||||
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::<String, ReflectSerialize>();
|
||||
registry.register::<[usize; 3]>();
|
||||
registry.register::<(f32, f32)>();
|
||||
registry.register::<MyEntityRef>();
|
||||
registry.register::<Entity>();
|
||||
registry.register::<MyResource>();
|
||||
}
|
||||
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::<AppTypeRegistry>();
|
||||
|
||||
let scene = DynamicScene::from_world(&world, registry);
|
||||
|
||||
let serialized = scene
|
||||
.serialize_ron(&world.resource::<AppTypeRegistry>().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<Foo>>()
|
||||
.get_single(&dst_world)
|
||||
.cloned()
|
||||
.unwrap();
|
||||
let foo = dst_world
|
||||
.query_filtered::<Entity, With<Foo>>()
|
||||
.get_single(&dst_world)
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(foo, bar_to_foo.0);
|
||||
assert!(dst_world
|
||||
.query_filtered::<&MyEntityRef, With<Foo>>()
|
||||
.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",);
|
||||
|
||||
|
||||
@ -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,
|
||||
|
||||
@ -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,
|
||||
|
||||
@ -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,
|
||||
|
||||
@ -76,6 +76,7 @@ pub const DEFAULT_FONT_HANDLE: HandleUntyped =
|
||||
impl Plugin for TextPlugin {
|
||||
fn build(&self, app: &mut App) {
|
||||
app.add_asset::<Font>()
|
||||
.add_debug_asset::<Font>()
|
||||
.add_asset::<FontAtlasSet>()
|
||||
.register_type::<Text>()
|
||||
.register_type::<Text2dBounds>()
|
||||
|
||||
@ -11,6 +11,7 @@ keywords = ["bevy"]
|
||||
[features]
|
||||
default = []
|
||||
serialize = ["serde"]
|
||||
bevy_ci_testing = ["bevy_app/bevy_ci_testing"]
|
||||
|
||||
[dependencies]
|
||||
# bevy
|
||||
|
||||
@ -47,6 +47,19 @@ impl Plugin for TimePlugin {
|
||||
.init_resource::<FixedTime>()
|
||||
.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::<bevy_app::ci_testing::CiTestingConfig>()
|
||||
{
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -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<Camera>)]
|
||||
#[reflect(Component, FromReflect, Default)]
|
||||
pub struct UiCameraConfig {
|
||||
/// Whether to output UI to this camera view.
|
||||
///
|
||||
|
||||
@ -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<Vec2>,
|
||||
@ -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,
|
||||
|
||||
@ -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,
|
||||
|
||||
@ -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<Option<f32>>, available: Size<AvailableSpace>| {
|
||||
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<PrimaryWindow>>,
|
||||
@ -244,18 +219,11 @@ pub fn ui_layout_system(
|
||||
mut resize_events: EventReader<bevy_window::WindowResized>,
|
||||
mut ui_surface: ResMut<UiSurface>,
|
||||
root_node_query: Query<Entity, (With<Node>, Without<Parent>)>,
|
||||
full_node_query: Query<(Entity, &Style, Option<&CalculatedSize>), With<Node>>,
|
||||
changed_style_query: Query<
|
||||
(Entity, &Style),
|
||||
(With<Node>, Without<CalculatedSize>, Changed<Style>),
|
||||
>,
|
||||
changed_size_query: Query<
|
||||
(Entity, &Style, &CalculatedSize),
|
||||
(With<Node>, Or<(Changed<CalculatedSize>, Changed<Style>)>),
|
||||
>,
|
||||
style_query: Query<(Entity, Ref<Style>), With<Node>>,
|
||||
mut measure_query: Query<(Entity, &mut ContentSize)>,
|
||||
children_query: Query<(Entity, &Children), (With<Node>, Changed<Children>)>,
|
||||
mut removed_children: RemovedComponents<Children>,
|
||||
mut removed_calculated_sizes: RemovedComponents<CalculatedSize>,
|
||||
mut removed_content_sizes: RemovedComponents<ContentSize>,
|
||||
mut node_transform_query: Query<(Entity, &mut Node, &mut Transform, Option<&Parent>)>,
|
||||
mut removed_nodes: RemovedComponents<Node>,
|
||||
) {
|
||||
@ -285,35 +253,34 @@ pub fn ui_layout_system(
|
||||
}
|
||||
|
||||
let scale_factor = logical_to_physical_factor * ui_scale.scale;
|
||||
|
||||
let layout_context = LayoutContext::new(scale_factor, physical_size);
|
||||
|
||||
if !scale_factor_events.is_empty() || ui_scale.is_changed() || resized {
|
||||
scale_factor_events.clear();
|
||||
// update all nodes
|
||||
for (entity, style, calculated_size) in &full_node_query {
|
||||
if let Some(calculated_size) = calculated_size {
|
||||
ui_surface.upsert_leaf(entity, style, calculated_size, &layout_context);
|
||||
} else {
|
||||
ui_surface.upsert_node(entity, style, &layout_context);
|
||||
}
|
||||
for (entity, style) in style_query.iter() {
|
||||
ui_surface.upsert_node(entity, &style, &layout_context);
|
||||
}
|
||||
} else {
|
||||
// update changed nodes without a calculated size
|
||||
for (entity, style) in changed_style_query.iter() {
|
||||
ui_surface.upsert_node(entity, style, &layout_context);
|
||||
for (entity, style) in style_query.iter() {
|
||||
if style.is_changed() {
|
||||
ui_surface.upsert_node(entity, &style, &layout_context);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// update changed nodes with a calculated size
|
||||
for (entity, style, calculated_size) in changed_size_query.iter() {
|
||||
ui_surface.upsert_leaf(entity, style, calculated_size, &layout_context);
|
||||
for (entity, mut content_size) in measure_query.iter_mut() {
|
||||
if let Some(measure_func) = content_size.measure_func.take() {
|
||||
ui_surface.update_measure(entity, measure_func);
|
||||
}
|
||||
}
|
||||
|
||||
// clean up removed nodes
|
||||
ui_surface.remove_entities(removed_nodes.iter());
|
||||
|
||||
// When a `CalculatedSize` component is removed from an entity, we need to remove the measure from the corresponding taffy node.
|
||||
for entity in removed_calculated_sizes.iter() {
|
||||
// When a `ContentSize` component is removed from an entity, we need to remove the measure from the corresponding taffy node.
|
||||
for entity in removed_content_sizes.iter() {
|
||||
ui_surface.try_remove_measure(entity);
|
||||
}
|
||||
|
||||
|
||||
@ -28,6 +28,7 @@ pub use layout::*;
|
||||
pub use measurement::*;
|
||||
pub use render::*;
|
||||
pub use ui_node::*;
|
||||
use widget::UiImageSize;
|
||||
|
||||
#[doc(hidden)]
|
||||
pub mod prelude {
|
||||
@ -87,7 +88,7 @@ impl Plugin for UiPlugin {
|
||||
.register_type::<AlignContent>()
|
||||
.register_type::<AlignItems>()
|
||||
.register_type::<AlignSelf>()
|
||||
.register_type::<CalculatedSize>()
|
||||
.register_type::<ContentSize>()
|
||||
.register_type::<Direction>()
|
||||
.register_type::<Display>()
|
||||
.register_type::<FlexDirection>()
|
||||
@ -113,6 +114,7 @@ impl Plugin for UiPlugin {
|
||||
.register_type::<Style>()
|
||||
.register_type::<BackgroundColor>()
|
||||
.register_type::<UiImage>()
|
||||
.register_type::<UiImageSize>()
|
||||
.register_type::<Val>()
|
||||
.register_type::<widget::Button>()
|
||||
.register_type::<widget::Label>()
|
||||
@ -142,7 +144,7 @@ impl Plugin for UiPlugin {
|
||||
#[cfg(feature = "bevy_text")]
|
||||
app.add_plugin(accessibility::AccessibilityPlugin);
|
||||
app.add_systems(PostUpdate, {
|
||||
let system = widget::update_image_calculated_size_system.before(UiSystem::Layout);
|
||||
let system = widget::update_image_content_size_system.before(UiSystem::Layout);
|
||||
// Potential conflicts: `Assets<Image>`
|
||||
// They run independently since `widget::image_node_system` will only ever observe
|
||||
// its own UiImage, and `widget::text_system` & `bevy_text::update_text2d_layout`
|
||||
|
||||
@ -1,12 +1,13 @@
|
||||
use bevy_ecs::prelude::Component;
|
||||
use bevy_ecs::reflect::ReflectComponent;
|
||||
use bevy_math::Vec2;
|
||||
use bevy_reflect::Reflect;
|
||||
use std::fmt::Formatter;
|
||||
pub use taffy::style::AvailableSpace;
|
||||
|
||||
impl std::fmt::Debug for CalculatedSize {
|
||||
impl std::fmt::Debug for ContentSize {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
||||
f.debug_struct("CalculatedSize").finish()
|
||||
f.debug_struct("ContentSize").finish()
|
||||
}
|
||||
}
|
||||
|
||||
@ -21,9 +22,6 @@ pub trait Measure: Send + Sync + 'static {
|
||||
available_width: AvailableSpace,
|
||||
available_height: AvailableSpace,
|
||||
) -> Vec2;
|
||||
|
||||
/// Clone and box self.
|
||||
fn dyn_clone(&self) -> Box<dyn Measure>;
|
||||
}
|
||||
|
||||
/// A `FixedMeasure` is a `Measure` that ignores all constraints and
|
||||
@ -43,35 +41,42 @@ impl Measure for FixedMeasure {
|
||||
) -> Vec2 {
|
||||
self.size
|
||||
}
|
||||
|
||||
fn dyn_clone(&self) -> Box<dyn Measure> {
|
||||
Box::new(self.clone())
|
||||
}
|
||||
}
|
||||
|
||||
/// A node with a `CalculatedSize` component is a node where its size
|
||||
/// A node with a `ContentSize` component is a node where its size
|
||||
/// is based on its content.
|
||||
#[derive(Component, Reflect)]
|
||||
pub struct CalculatedSize {
|
||||
#[reflect(Component)]
|
||||
pub struct ContentSize {
|
||||
/// The `Measure` used to compute the intrinsic size
|
||||
#[reflect(ignore)]
|
||||
pub measure: Box<dyn Measure>,
|
||||
pub(crate) measure_func: Option<taffy::node::MeasureFunc>,
|
||||
}
|
||||
|
||||
impl ContentSize {
|
||||
/// Set a `Measure` for this function
|
||||
pub fn set(&mut self, measure: impl Measure) {
|
||||
let measure_func =
|
||||
move |size: taffy::prelude::Size<Option<f32>>,
|
||||
available: taffy::prelude::Size<AvailableSpace>| {
|
||||
let size =
|
||||
measure.measure(size.width, size.height, available.width, available.height);
|
||||
taffy::prelude::Size {
|
||||
width: size.x,
|
||||
height: size.y,
|
||||
}
|
||||
};
|
||||
self.measure_func = Some(taffy::node::MeasureFunc::Boxed(Box::new(measure_func)));
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(clippy::derivable_impls)]
|
||||
impl Default for CalculatedSize {
|
||||
impl Default for ContentSize {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
// Default `FixedMeasure` always returns zero size.
|
||||
measure: Box::<FixedMeasure>::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Clone for CalculatedSize {
|
||||
fn clone(&self) -> Self {
|
||||
Self {
|
||||
measure: self.measure.dyn_clone(),
|
||||
measure_func: Some(taffy::node::MeasureFunc::Raw(|_, _| {
|
||||
taffy::prelude::Size::ZERO
|
||||
})),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -2,7 +2,7 @@
|
||||
|
||||
use crate::{
|
||||
widget::{Button, UiImageSize},
|
||||
BackgroundColor, CalculatedSize, FocusPolicy, Interaction, Node, Style, UiImage, ZIndex,
|
||||
BackgroundColor, ContentSize, FocusPolicy, Interaction, Node, Style, UiImage, ZIndex,
|
||||
};
|
||||
use bevy_ecs::bundle::Bundle;
|
||||
use bevy_render::{
|
||||
@ -63,7 +63,7 @@ impl Default for NodeBundle {
|
||||
}
|
||||
|
||||
/// A UI node that is an image
|
||||
#[derive(Bundle, Clone, Debug, Default)]
|
||||
#[derive(Bundle, Debug, Default)]
|
||||
pub struct ImageBundle {
|
||||
/// Describes the logical size of the node
|
||||
///
|
||||
@ -74,7 +74,7 @@ pub struct ImageBundle {
|
||||
/// In some cases these styles also affect how the node drawn/painted.
|
||||
pub style: Style,
|
||||
/// The calculated size based on the given image
|
||||
pub calculated_size: CalculatedSize,
|
||||
pub calculated_size: ContentSize,
|
||||
/// The background color, which serves as a "fill" for this node
|
||||
///
|
||||
/// Combines with `UiImage` to tint the provided image.
|
||||
@ -107,7 +107,7 @@ pub struct ImageBundle {
|
||||
|
||||
#[cfg(feature = "bevy_text")]
|
||||
/// A UI node that is text
|
||||
#[derive(Bundle, Clone, Debug)]
|
||||
#[derive(Bundle, Debug)]
|
||||
pub struct TextBundle {
|
||||
/// Describes the logical size of the node
|
||||
pub node: Node,
|
||||
@ -119,7 +119,7 @@ pub struct TextBundle {
|
||||
/// Text layout information
|
||||
pub text_layout_info: TextLayoutInfo,
|
||||
/// The calculated size based on the given image
|
||||
pub calculated_size: CalculatedSize,
|
||||
pub calculated_size: ContentSize,
|
||||
/// Whether this node should block interaction with lower nodes
|
||||
pub focus_policy: FocusPolicy,
|
||||
/// The transform of the node
|
||||
|
||||
@ -3,6 +3,7 @@ use bevy_asset::Handle;
|
||||
use bevy_ecs::{prelude::Component, reflect::ReflectComponent};
|
||||
use bevy_math::{Rect, Vec2};
|
||||
use bevy_reflect::prelude::*;
|
||||
use bevy_reflect::ReflectFromReflect;
|
||||
use bevy_render::{
|
||||
color::Color,
|
||||
texture::{Image, DEFAULT_IMAGE_HANDLE},
|
||||
@ -62,8 +63,8 @@ impl Default for Node {
|
||||
///
|
||||
/// This enum allows specifying values for various [`Style`] properties in different units,
|
||||
/// such as logical pixels, percentages, or automatically determined values.
|
||||
#[derive(Copy, Clone, PartialEq, Debug, Serialize, Deserialize, Reflect)]
|
||||
#[reflect(PartialEq, Serialize, Deserialize)]
|
||||
#[derive(Copy, Clone, PartialEq, Debug, Serialize, Deserialize, Reflect, FromReflect)]
|
||||
#[reflect(FromReflect, PartialEq, Serialize, Deserialize)]
|
||||
pub enum Val {
|
||||
/// Automatically determine the value based on the context and other `Style` properties.
|
||||
Auto,
|
||||
@ -278,8 +279,8 @@ impl Val {
|
||||
/// - [A Complete Guide To CSS Grid](https://css-tricks.com/snippets/css/complete-guide-grid/) by CSS Tricks. This is detailed guide with illustrations and comphrehensive written explanation of the different CSS Grid properties and how they work.
|
||||
/// - [CSS Grid Garden](https://cssgridgarden.com/). An interactive tutorial/game that teaches the essential parts of CSS Grid in a fun engaging way.
|
||||
|
||||
#[derive(Component, Clone, PartialEq, Debug, Reflect)]
|
||||
#[reflect(Component, Default, PartialEq)]
|
||||
#[derive(Component, Clone, PartialEq, Debug, Reflect, FromReflect)]
|
||||
#[reflect(Component, FromReflect, Default, PartialEq)]
|
||||
pub struct Style {
|
||||
/// Which layout algorithm to use when laying out this node's contents:
|
||||
/// - [`Display::Flex`]: Use the Flexbox layout algorithm
|
||||
@ -598,8 +599,8 @@ impl Default for Style {
|
||||
}
|
||||
|
||||
/// How items are aligned according to the cross axis
|
||||
#[derive(Copy, Clone, PartialEq, Eq, Debug, Serialize, Deserialize, Reflect)]
|
||||
#[reflect(PartialEq, Serialize, Deserialize)]
|
||||
#[derive(Copy, Clone, PartialEq, Eq, Debug, Serialize, Deserialize, Reflect, FromReflect)]
|
||||
#[reflect(FromReflect, PartialEq, Serialize, Deserialize)]
|
||||
pub enum AlignItems {
|
||||
/// The items are packed in their default position as if no alignment was applied
|
||||
Default,
|
||||
@ -632,8 +633,8 @@ impl Default for AlignItems {
|
||||
}
|
||||
|
||||
/// How items are aligned according to the cross axis
|
||||
#[derive(Copy, Clone, PartialEq, Eq, Debug, Serialize, Deserialize, Reflect)]
|
||||
#[reflect(PartialEq, Serialize, Deserialize)]
|
||||
#[derive(Copy, Clone, PartialEq, Eq, Debug, Serialize, Deserialize, Reflect, FromReflect)]
|
||||
#[reflect(FromReflect, PartialEq, Serialize, Deserialize)]
|
||||
pub enum JustifyItems {
|
||||
/// The items are packed in their default position as if no alignment was applied
|
||||
Default,
|
||||
@ -661,8 +662,8 @@ impl Default for JustifyItems {
|
||||
|
||||
/// How this item is aligned according to the cross axis.
|
||||
/// Overrides [`AlignItems`].
|
||||
#[derive(Copy, Clone, PartialEq, Eq, Debug, Serialize, Deserialize, Reflect)]
|
||||
#[reflect(PartialEq, Serialize, Deserialize)]
|
||||
#[derive(Copy, Clone, PartialEq, Eq, Debug, Serialize, Deserialize, Reflect, FromReflect)]
|
||||
#[reflect(FromReflect, PartialEq, Serialize, Deserialize)]
|
||||
pub enum AlignSelf {
|
||||
/// Use the parent node's [`AlignItems`] value to determine how this item should be aligned.
|
||||
Auto,
|
||||
@ -696,8 +697,8 @@ impl Default for AlignSelf {
|
||||
|
||||
/// How this item is aligned according to the cross axis.
|
||||
/// Overrides [`AlignItems`].
|
||||
#[derive(Copy, Clone, PartialEq, Eq, Debug, Serialize, Deserialize, Reflect)]
|
||||
#[reflect(PartialEq, Serialize, Deserialize)]
|
||||
#[derive(Copy, Clone, PartialEq, Eq, Debug, Serialize, Deserialize, Reflect, FromReflect)]
|
||||
#[reflect(FromReflect, PartialEq, Serialize, Deserialize)]
|
||||
pub enum JustifySelf {
|
||||
/// Use the parent node's [`AlignItems`] value to determine how this item should be aligned.
|
||||
Auto,
|
||||
@ -726,8 +727,8 @@ impl Default for JustifySelf {
|
||||
/// Defines how each line is aligned within the flexbox.
|
||||
///
|
||||
/// It only applies if [`FlexWrap::Wrap`] is present and if there are multiple lines of items.
|
||||
#[derive(Copy, Clone, PartialEq, Eq, Debug, Serialize, Deserialize, Reflect)]
|
||||
#[reflect(PartialEq, Serialize, Deserialize)]
|
||||
#[derive(Copy, Clone, PartialEq, Eq, Debug, Serialize, Deserialize, Reflect, FromReflect)]
|
||||
#[reflect(FromReflect, PartialEq, Serialize, Deserialize)]
|
||||
pub enum AlignContent {
|
||||
/// The items are packed in their default position as if no alignment was applied
|
||||
Default,
|
||||
@ -765,8 +766,8 @@ impl Default for AlignContent {
|
||||
}
|
||||
|
||||
/// Defines how items are aligned according to the main axis
|
||||
#[derive(Copy, Clone, PartialEq, Eq, Debug, Serialize, Deserialize, Reflect)]
|
||||
#[reflect(PartialEq, Serialize, Deserialize)]
|
||||
#[derive(Copy, Clone, PartialEq, Eq, Debug, Serialize, Deserialize, Reflect, FromReflect)]
|
||||
#[reflect(FromReflect, PartialEq, Serialize, Deserialize)]
|
||||
pub enum JustifyContent {
|
||||
/// The items are packed in their default position as if no alignment was applied
|
||||
Default,
|
||||
@ -801,8 +802,8 @@ impl Default for JustifyContent {
|
||||
/// Defines the text direction
|
||||
///
|
||||
/// For example English is written LTR (left-to-right) while Arabic is written RTL (right-to-left).
|
||||
#[derive(Copy, Clone, PartialEq, Eq, Debug, Serialize, Deserialize, Reflect)]
|
||||
#[reflect(PartialEq, Serialize, Deserialize)]
|
||||
#[derive(Copy, Clone, PartialEq, Eq, Debug, Serialize, Deserialize, Reflect, FromReflect)]
|
||||
#[reflect(FromReflect, PartialEq, Serialize, Deserialize)]
|
||||
pub enum Direction {
|
||||
/// Inherit from parent node.
|
||||
Inherit,
|
||||
@ -825,8 +826,8 @@ impl Default for Direction {
|
||||
/// Whether to use a Flexbox layout model.
|
||||
///
|
||||
/// Part of the [`Style`] component.
|
||||
#[derive(Copy, Clone, PartialEq, Eq, Debug, Serialize, Deserialize, Reflect)]
|
||||
#[reflect(PartialEq, Serialize, Deserialize)]
|
||||
#[derive(Copy, Clone, PartialEq, Eq, Debug, Serialize, Deserialize, Reflect, FromReflect)]
|
||||
#[reflect(FromReflect, PartialEq, Serialize, Deserialize)]
|
||||
pub enum Display {
|
||||
/// Use Flexbox layout model to determine the position of this [`Node`].
|
||||
Flex,
|
||||
@ -850,8 +851,8 @@ impl Default for Display {
|
||||
}
|
||||
|
||||
/// Defines how flexbox items are ordered within a flexbox
|
||||
#[derive(Copy, Clone, PartialEq, Eq, Debug, Serialize, Deserialize, Reflect)]
|
||||
#[reflect(PartialEq, Serialize, Deserialize)]
|
||||
#[derive(Copy, Clone, PartialEq, Eq, Debug, Serialize, Deserialize, Reflect, FromReflect)]
|
||||
#[reflect(FromReflect, PartialEq, Serialize, Deserialize)]
|
||||
pub enum FlexDirection {
|
||||
/// Same way as text direction along the main axis.
|
||||
Row,
|
||||
@ -874,8 +875,8 @@ impl Default for FlexDirection {
|
||||
}
|
||||
|
||||
/// Whether to show or hide overflowing items
|
||||
#[derive(Copy, Clone, PartialEq, Eq, Debug, Reflect, Serialize, Deserialize)]
|
||||
#[reflect(PartialEq, Serialize, Deserialize)]
|
||||
#[derive(Copy, Clone, PartialEq, Eq, Debug, Reflect, Serialize, Deserialize, FromReflect)]
|
||||
#[reflect(FromReflect, PartialEq, Serialize, Deserialize)]
|
||||
pub struct Overflow {
|
||||
/// Whether to show or clip overflowing items on the x axis
|
||||
pub x: OverflowAxis,
|
||||
@ -934,8 +935,8 @@ impl Default for Overflow {
|
||||
}
|
||||
|
||||
/// Whether to show or hide overflowing items
|
||||
#[derive(Copy, Clone, PartialEq, Eq, Debug, Reflect, Serialize, Deserialize)]
|
||||
#[reflect(PartialEq, Serialize, Deserialize)]
|
||||
#[derive(Copy, Clone, PartialEq, Eq, Debug, Reflect, FromReflect, Serialize, Deserialize)]
|
||||
#[reflect(FromReflect, PartialEq, Serialize, Deserialize)]
|
||||
pub enum OverflowAxis {
|
||||
/// Show overflowing items.
|
||||
Visible,
|
||||
@ -959,8 +960,8 @@ impl Default for OverflowAxis {
|
||||
}
|
||||
|
||||
/// The strategy used to position this node
|
||||
#[derive(Copy, Clone, PartialEq, Eq, Debug, Serialize, Deserialize, Reflect)]
|
||||
#[reflect(PartialEq, Serialize, Deserialize)]
|
||||
#[derive(Copy, Clone, PartialEq, Eq, Debug, Serialize, Deserialize, Reflect, FromReflect)]
|
||||
#[reflect(FromReflect, PartialEq, Serialize, Deserialize)]
|
||||
pub enum PositionType {
|
||||
/// Relative to all other nodes with the [`PositionType::Relative`] value.
|
||||
Relative,
|
||||
@ -981,8 +982,8 @@ impl Default for PositionType {
|
||||
}
|
||||
|
||||
/// Defines if flexbox items appear on a single line or on multiple lines
|
||||
#[derive(Copy, Clone, PartialEq, Eq, Debug, Serialize, Deserialize, Reflect)]
|
||||
#[reflect(PartialEq, Serialize, Deserialize)]
|
||||
#[derive(Copy, Clone, PartialEq, Eq, Debug, Serialize, Deserialize, Reflect, FromReflect)]
|
||||
#[reflect(FromReflect, PartialEq, Serialize, Deserialize)]
|
||||
pub enum FlexWrap {
|
||||
/// Single line, will overflow if needed.
|
||||
NoWrap,
|
||||
@ -1009,8 +1010,8 @@ impl Default for FlexWrap {
|
||||
/// Defaults to [`GridAutoFlow::Row`]
|
||||
///
|
||||
/// <https://developer.mozilla.org/en-US/docs/Web/CSS/grid-auto-flow>
|
||||
#[derive(Copy, Clone, PartialEq, Eq, Debug, Serialize, Deserialize, Reflect)]
|
||||
#[reflect(PartialEq, Serialize, Deserialize)]
|
||||
#[derive(Copy, Clone, PartialEq, Eq, Debug, Serialize, Deserialize, Reflect, FromReflect)]
|
||||
#[reflect(FromReflect, PartialEq, Serialize, Deserialize)]
|
||||
pub enum GridAutoFlow {
|
||||
/// Items are placed by filling each row in turn, adding new rows as necessary
|
||||
Row,
|
||||
@ -1033,7 +1034,7 @@ impl Default for GridAutoFlow {
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, PartialEq, Debug, Serialize, Deserialize, Reflect, FromReflect)]
|
||||
#[reflect_value(PartialEq, Serialize, Deserialize)]
|
||||
#[reflect_value(FromReflect, PartialEq, Serialize, Deserialize)]
|
||||
pub enum MinTrackSizingFunction {
|
||||
/// Track minimum size should be a fixed pixel value
|
||||
Px(f32),
|
||||
@ -1048,7 +1049,7 @@ pub enum MinTrackSizingFunction {
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, PartialEq, Debug, Serialize, Deserialize, Reflect, FromReflect)]
|
||||
#[reflect_value(PartialEq, Serialize, Deserialize)]
|
||||
#[reflect_value(FromReflect, PartialEq, Serialize, Deserialize)]
|
||||
pub enum MaxTrackSizingFunction {
|
||||
/// Track maximum size should be a fixed pixel value
|
||||
Px(f32),
|
||||
@ -1073,7 +1074,7 @@ pub enum MaxTrackSizingFunction {
|
||||
/// A [`GridTrack`] is a Row or Column of a CSS Grid. This struct specifies what size the track should be.
|
||||
/// See below for the different "track sizing functions" you can specify.
|
||||
#[derive(Copy, Clone, PartialEq, Debug, Serialize, Deserialize, Reflect, FromReflect)]
|
||||
#[reflect(PartialEq, Serialize, Deserialize)]
|
||||
#[reflect(FromReflect, PartialEq, Serialize, Deserialize)]
|
||||
pub struct GridTrack {
|
||||
pub(crate) min_sizing_function: MinTrackSizingFunction,
|
||||
pub(crate) max_sizing_function: MaxTrackSizingFunction,
|
||||
@ -1191,7 +1192,7 @@ impl Default for GridTrack {
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, PartialEq, Debug, Serialize, Deserialize, Reflect, FromReflect)]
|
||||
#[reflect(PartialEq, Serialize, Deserialize)]
|
||||
#[reflect(FromReflect, PartialEq, Serialize, Deserialize)]
|
||||
/// How many times to repeat a repeated grid track
|
||||
///
|
||||
/// <https://developer.mozilla.org/en-US/docs/Web/CSS/repeat>
|
||||
@ -1241,7 +1242,7 @@ impl From<usize> for GridTrackRepetition {
|
||||
/// then all track (in and outside of the repetition) must be fixed size (px or percent). Integer repetitions are just shorthand for writing out
|
||||
/// N tracks longhand and are not subject to the same limitations.
|
||||
#[derive(Clone, PartialEq, Debug, Serialize, Deserialize, Reflect, FromReflect)]
|
||||
#[reflect(PartialEq, Serialize, Deserialize)]
|
||||
#[reflect(FromReflect, PartialEq, Serialize, Deserialize)]
|
||||
pub struct RepeatedGridTrack {
|
||||
pub(crate) repetition: GridTrackRepetition,
|
||||
pub(crate) tracks: SmallVec<[GridTrack; 1]>,
|
||||
@ -1390,8 +1391,8 @@ impl From<RepeatedGridTrack> for Vec<RepeatedGridTrack> {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, PartialEq, Eq, Debug, Serialize, Deserialize, Reflect)]
|
||||
#[reflect(PartialEq, Serialize, Deserialize)]
|
||||
#[derive(Copy, Clone, PartialEq, Eq, Debug, Serialize, Deserialize, Reflect, FromReflect)]
|
||||
#[reflect(FromReflect, PartialEq, Serialize, Deserialize)]
|
||||
/// Represents the position of a grid item in a single axis.
|
||||
///
|
||||
/// There are 3 fields which may be set:
|
||||
@ -1513,8 +1514,8 @@ impl Default for GridPlacement {
|
||||
///
|
||||
/// This serves as the "fill" color.
|
||||
/// When combined with [`UiImage`], tints the provided texture.
|
||||
#[derive(Component, Copy, Clone, Debug, Reflect)]
|
||||
#[reflect(Component, Default)]
|
||||
#[derive(Component, Copy, Clone, Debug, Reflect, FromReflect)]
|
||||
#[reflect(FromReflect, Component, Default)]
|
||||
pub struct BackgroundColor(pub Color);
|
||||
|
||||
impl BackgroundColor {
|
||||
@ -1585,8 +1586,8 @@ impl From<Handle<Image>> for UiImage {
|
||||
}
|
||||
|
||||
/// The calculated clip of the node
|
||||
#[derive(Component, Default, Copy, Clone, Debug, Reflect)]
|
||||
#[reflect(Component)]
|
||||
#[derive(Component, Default, Copy, Clone, Debug, Reflect, FromReflect)]
|
||||
#[reflect(FromReflect, Component)]
|
||||
pub struct CalculatedClip {
|
||||
/// The rect of the clip
|
||||
pub clip: Rect,
|
||||
@ -1605,8 +1606,8 @@ pub struct CalculatedClip {
|
||||
/// [`ZIndex::Local(n)`] and [`ZIndex::Global(n)`] for root nodes.
|
||||
///
|
||||
/// Nodes without this component will be treated as if they had a value of [`ZIndex::Local(0)`].
|
||||
#[derive(Component, Copy, Clone, Debug, Reflect)]
|
||||
#[reflect(Component)]
|
||||
#[derive(Component, Copy, Clone, Debug, Reflect, FromReflect)]
|
||||
#[reflect(Component, FromReflect)]
|
||||
pub enum ZIndex {
|
||||
/// Indicates the order in which this node should be rendered relative to its siblings.
|
||||
Local(i32),
|
||||
|
||||
@ -1,9 +1,9 @@
|
||||
use bevy_ecs::prelude::Component;
|
||||
use bevy_ecs::reflect::ReflectComponent;
|
||||
use bevy_reflect::std_traits::ReflectDefault;
|
||||
use bevy_reflect::Reflect;
|
||||
use bevy_reflect::{FromReflect, Reflect, ReflectFromReflect};
|
||||
|
||||
/// Marker struct for buttons
|
||||
#[derive(Component, Debug, Default, Clone, Copy, Reflect)]
|
||||
#[reflect(Component, Default)]
|
||||
#[derive(Component, Debug, Default, Clone, Copy, Reflect, FromReflect)]
|
||||
#[reflect(Component, FromReflect, Default)]
|
||||
pub struct Button;
|
||||
|
||||
@ -1,21 +1,24 @@
|
||||
use crate::{measurement::AvailableSpace, CalculatedSize, Measure, Node, UiImage};
|
||||
use crate::{measurement::AvailableSpace, ContentSize, Measure, Node, UiImage};
|
||||
use bevy_asset::Assets;
|
||||
#[cfg(feature = "bevy_text")]
|
||||
use bevy_ecs::query::Without;
|
||||
use bevy_ecs::{
|
||||
prelude::Component,
|
||||
query::With,
|
||||
reflect::ReflectComponent,
|
||||
system::{Query, Res},
|
||||
};
|
||||
use bevy_math::Vec2;
|
||||
use bevy_reflect::{std_traits::ReflectDefault, FromReflect, Reflect, ReflectFromReflect};
|
||||
use bevy_render::texture::Image;
|
||||
#[cfg(feature = "bevy_text")]
|
||||
use bevy_text::Text;
|
||||
|
||||
/// The size of the image in pixels
|
||||
/// The size of the image in physical pixels
|
||||
///
|
||||
/// This field is set automatically
|
||||
#[derive(Component, Copy, Clone, Debug, Default)]
|
||||
/// This field is set automatically by `update_image_calculated_size_system`
|
||||
#[derive(Component, Debug, Copy, Clone, Default, Reflect, FromReflect)]
|
||||
#[reflect(Component, Default, FromReflect)]
|
||||
pub struct UiImageSize {
|
||||
size: Vec2,
|
||||
}
|
||||
@ -58,25 +61,21 @@ impl Measure for ImageMeasure {
|
||||
}
|
||||
size
|
||||
}
|
||||
|
||||
fn dyn_clone(&self) -> Box<dyn Measure> {
|
||||
Box::new(self.clone())
|
||||
}
|
||||
}
|
||||
|
||||
/// Updates calculated size of the node based on the image provided
|
||||
pub fn update_image_calculated_size_system(
|
||||
/// Updates content size of the node based on the image provided
|
||||
pub fn update_image_content_size_system(
|
||||
textures: Res<Assets<Image>>,
|
||||
#[cfg(feature = "bevy_text")] mut query: Query<
|
||||
(&mut CalculatedSize, &UiImage, &mut UiImageSize),
|
||||
(&mut ContentSize, &UiImage, &mut UiImageSize),
|
||||
(With<Node>, Without<Text>),
|
||||
>,
|
||||
#[cfg(not(feature = "bevy_text"))] mut query: Query<
|
||||
(&mut CalculatedSize, &UiImage, &mut UiImageSize),
|
||||
(&mut ContentSize, &UiImage, &mut UiImageSize),
|
||||
With<Node>,
|
||||
>,
|
||||
) {
|
||||
for (mut calculated_size, image, mut image_size) in &mut query {
|
||||
for (mut content_size, image, mut image_size) in &mut query {
|
||||
if let Some(texture) = textures.get(&image.texture) {
|
||||
let size = Vec2::new(
|
||||
texture.texture_descriptor.size.width as f32,
|
||||
@ -85,7 +84,7 @@ pub fn update_image_calculated_size_system(
|
||||
// Update only if size has changed to avoid needless layout calculations
|
||||
if size != image_size.size {
|
||||
image_size.size = size;
|
||||
calculated_size.measure = Box::new(ImageMeasure { size });
|
||||
content_size.set(ImageMeasure { size });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,9 +1,9 @@
|
||||
use bevy_ecs::prelude::Component;
|
||||
use bevy_ecs::reflect::ReflectComponent;
|
||||
use bevy_reflect::std_traits::ReflectDefault;
|
||||
use bevy_reflect::Reflect;
|
||||
use bevy_reflect::{FromReflect, Reflect, ReflectFromReflect};
|
||||
|
||||
/// Marker struct for labels
|
||||
#[derive(Component, Debug, Default, Clone, Copy, Reflect)]
|
||||
#[reflect(Component, Default)]
|
||||
#[derive(Component, Debug, Default, Clone, Copy, Reflect, FromReflect)]
|
||||
#[reflect(Component, FromReflect, Default)]
|
||||
pub struct Label;
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
use crate::{CalculatedSize, Measure, Node, UiScale};
|
||||
use crate::{ContentSize, Measure, Node, UiScale};
|
||||
use bevy_asset::Assets;
|
||||
use bevy_ecs::{
|
||||
entity::Entity,
|
||||
@ -52,10 +52,6 @@ impl Measure for TextMeasure {
|
||||
)
|
||||
.ceil()
|
||||
}
|
||||
|
||||
fn dyn_clone(&self) -> Box<dyn Measure> {
|
||||
Box::new(self.clone())
|
||||
}
|
||||
}
|
||||
|
||||
/// Creates a `Measure` for text nodes that allows the UI to determine the appropriate amount of space
|
||||
@ -70,7 +66,7 @@ pub fn measure_text_system(
|
||||
mut text_queries: ParamSet<(
|
||||
Query<Entity, (Changed<Text>, With<Node>)>,
|
||||
Query<Entity, (With<Text>, With<Node>)>,
|
||||
Query<(&Text, &mut CalculatedSize)>,
|
||||
Query<(&Text, &mut ContentSize)>,
|
||||
)>,
|
||||
) {
|
||||
let window_scale_factor = windows
|
||||
@ -103,7 +99,7 @@ pub fn measure_text_system(
|
||||
let mut new_queue = Vec::new();
|
||||
let mut query = text_queries.p2();
|
||||
for entity in queued_text.drain(..) {
|
||||
if let Ok((text, mut calculated_size)) = query.get_mut(entity) {
|
||||
if let Ok((text, mut content_size)) = query.get_mut(entity) {
|
||||
match text_pipeline.create_text_measure(
|
||||
&fonts,
|
||||
&text.sections,
|
||||
@ -112,7 +108,7 @@ pub fn measure_text_system(
|
||||
text.linebreak_behavior,
|
||||
) {
|
||||
Ok(measure) => {
|
||||
calculated_size.measure = Box::new(TextMeasure { info: measure });
|
||||
content_size.set(TextMeasure { info: measure });
|
||||
}
|
||||
Err(TextError::NoSuchFont) => {
|
||||
new_queue.push(entity);
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
use bevy_ecs::{
|
||||
entity::{Entity, EntityMap, MapEntities, MapEntitiesError},
|
||||
entity::{Entity, EntityMapper, MapEntities},
|
||||
prelude::{Component, ReflectComponent},
|
||||
};
|
||||
use bevy_math::{DVec2, IVec2, Vec2};
|
||||
@ -55,14 +55,13 @@ impl WindowRef {
|
||||
}
|
||||
|
||||
impl MapEntities for WindowRef {
|
||||
fn map_entities(&mut self, entity_map: &EntityMap) -> Result<(), MapEntitiesError> {
|
||||
fn map_entities(&mut self, entity_mapper: &mut EntityMapper) {
|
||||
match self {
|
||||
Self::Entity(entity) => {
|
||||
*entity = entity_map.get(*entity)?;
|
||||
Ok(())
|
||||
*entity = entity_mapper.get_or_reserve(*entity);
|
||||
}
|
||||
Self::Primary => Ok(()),
|
||||
}
|
||||
Self::Primary => {}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@ -145,7 +144,7 @@ pub struct Window {
|
||||
/// The "html canvas" element selector.
|
||||
///
|
||||
/// If set, this selector will be used to find a matching html canvas element,
|
||||
/// rather than creating a new one.
|
||||
/// rather than creating a new one.
|
||||
/// Uses the [CSS selector format](https://developer.mozilla.org/en-US/docs/Web/API/Document/querySelector).
|
||||
///
|
||||
/// This value has no effect on non-web platforms.
|
||||
|
||||
@ -57,6 +57,8 @@ The default feature set enables most of the expected features of a game engine,
|
||||
|minimp3|MP3 audio format support (through minimp3)|
|
||||
|mp3|MP3 audio format support|
|
||||
|serialize|Enable serialization support through serde|
|
||||
|shader_format_glsl|Enable support for shaders in GLSL|
|
||||
|shader_format_spirv|Enable support for shaders in SPIR-V|
|
||||
|subpixel_glyph_atlas|Enable rendering of font glyphs using subpixel accuracy|
|
||||
|symphonia-aac|AAC audio format support (through symphonia)|
|
||||
|symphonia-all|AAC, FLAC, MP3, MP4, OGG/VORBIS, and WAV audio formats support (through symphonia)|
|
||||
|
||||
@ -43,15 +43,23 @@ fn setup(
|
||||
transform: Transform::from_xyz(4.0, 8.0, 4.0),
|
||||
..default()
|
||||
});
|
||||
// text
|
||||
commands.spawn(TextBundle::from_section(
|
||||
"Press 't' to toggle drawing gizmos on top of everything else in the scene",
|
||||
TextStyle {
|
||||
font_size: 24.,
|
||||
color: Color::WHITE,
|
||||
|
||||
// example instructions
|
||||
commands.spawn(
|
||||
TextBundle::from_section(
|
||||
"Press 't' to toggle drawing gizmos on top of everything else in the scene",
|
||||
TextStyle {
|
||||
font_size: 20.,
|
||||
..default()
|
||||
},
|
||||
)
|
||||
.with_style(Style {
|
||||
position_type: PositionType::Absolute,
|
||||
top: Val::Px(12.0),
|
||||
left: Val::Px(12.0),
|
||||
..default()
|
||||
},
|
||||
));
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
fn system(mut gizmos: Gizmos, time: Res<Time>) {
|
||||
|
||||
@ -324,13 +324,12 @@ fn setup(
|
||||
},
|
||||
));
|
||||
|
||||
// UI
|
||||
// example instructions
|
||||
commands.spawn(
|
||||
TextBundle::from_section(
|
||||
"",
|
||||
TextStyle {
|
||||
font_size: 20.0,
|
||||
color: Color::BLACK,
|
||||
font_size: 20.,
|
||||
..default()
|
||||
},
|
||||
)
|
||||
|
||||
@ -97,15 +97,15 @@ fn setup_instructions(mut commands: Commands) {
|
||||
commands.spawn((TextBundle::from_section(
|
||||
"Press Spacebar to Toggle Atmospheric Fog.\nPress S to Toggle Directional Light Fog Influence.",
|
||||
TextStyle {
|
||||
font_size: 15.0,
|
||||
font_size: 20.0,
|
||||
color: Color::WHITE,
|
||||
..default()
|
||||
},
|
||||
)
|
||||
.with_style(Style {
|
||||
position_type: PositionType::Absolute,
|
||||
bottom: Val::Px(10.0),
|
||||
left: Val::Px(10.0),
|
||||
bottom: Val::Px(12.0),
|
||||
left: Val::Px(12.0),
|
||||
..default()
|
||||
}),));
|
||||
}
|
||||
|
||||
@ -91,19 +91,20 @@ fn setup_scene(
|
||||
}
|
||||
}
|
||||
|
||||
// example instructions
|
||||
commands.spawn(
|
||||
TextBundle::from_section(
|
||||
"",
|
||||
TextStyle {
|
||||
font_size: 18.0,
|
||||
font_size: 20.0,
|
||||
color: Color::BLACK,
|
||||
..default()
|
||||
},
|
||||
)
|
||||
.with_style(Style {
|
||||
position_type: PositionType::Absolute,
|
||||
bottom: Val::Px(10.0),
|
||||
left: Val::Px(10.0),
|
||||
bottom: Val::Px(12.0),
|
||||
left: Val::Px(12.0),
|
||||
..default()
|
||||
}),
|
||||
);
|
||||
|
||||
@ -138,20 +138,21 @@ fn setup_pyramid_scene(
|
||||
}
|
||||
|
||||
fn setup_instructions(mut commands: Commands) {
|
||||
commands.spawn((TextBundle::from_section(
|
||||
"",
|
||||
TextStyle {
|
||||
font_size: 15.0,
|
||||
color: Color::WHITE,
|
||||
commands.spawn(
|
||||
TextBundle::from_section(
|
||||
"",
|
||||
TextStyle {
|
||||
font_size: 20.0,
|
||||
..default()
|
||||
},
|
||||
)
|
||||
.with_style(Style {
|
||||
position_type: PositionType::Absolute,
|
||||
top: Val::Px(12.0),
|
||||
left: Val::Px(12.0),
|
||||
..default()
|
||||
},
|
||||
)
|
||||
.with_style(Style {
|
||||
position_type: PositionType::Absolute,
|
||||
top: Val::Px(10.0),
|
||||
left: Val::Px(10.0),
|
||||
..default()
|
||||
}),));
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
fn update_system(
|
||||
|
||||
@ -232,6 +232,23 @@ fn setup(
|
||||
transform: Transform::from_xyz(-2.0, 2.5, 5.0).looking_at(Vec3::ZERO, Vec3::Y),
|
||||
..default()
|
||||
});
|
||||
|
||||
// example instructions
|
||||
commands.spawn(
|
||||
TextBundle::from_section(
|
||||
"Use arrow keys to move objects",
|
||||
TextStyle {
|
||||
font_size: 20.0,
|
||||
..default()
|
||||
},
|
||||
)
|
||||
.with_style(Style {
|
||||
position_type: PositionType::Absolute,
|
||||
top: Val::Px(12.0),
|
||||
left: Val::Px(12.0),
|
||||
..default()
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
fn animate_light_direction(
|
||||
|
||||
@ -315,11 +315,11 @@ fn setup(
|
||||
commands.spawn(background_cube_bundle(Vec3::new(0., 0., -45.)));
|
||||
|
||||
let style = TextStyle {
|
||||
font_size: 18.0,
|
||||
color: Color::WHITE,
|
||||
font_size: 20.0,
|
||||
..default()
|
||||
};
|
||||
|
||||
// example instructions
|
||||
commands.spawn(
|
||||
TextBundle::from_sections(vec![
|
||||
TextSection::new(
|
||||
@ -332,8 +332,7 @@ fn setup(
|
||||
),
|
||||
TextSection::new(format!("{parallax_mapping_method}\n"), style.clone()),
|
||||
TextSection::new("\n\n", style.clone()),
|
||||
TextSection::new("Controls\n", style.clone()),
|
||||
TextSection::new("---------------\n", style.clone()),
|
||||
TextSection::new("Controls:\n", style.clone()),
|
||||
TextSection::new("Left click - Change view angle\n", style.clone()),
|
||||
TextSection::new(
|
||||
"1/2 - Decrease/Increase parallax depth scale\n",
|
||||
@ -344,8 +343,8 @@ fn setup(
|
||||
])
|
||||
.with_style(Style {
|
||||
position_type: PositionType::Absolute,
|
||||
top: Val::Px(10.0),
|
||||
left: Val::Px(10.0),
|
||||
top: Val::Px(12.0),
|
||||
left: Val::Px(12.0),
|
||||
..default()
|
||||
}),
|
||||
);
|
||||
|
||||
@ -5,18 +5,6 @@ use std::f32::consts::PI;
|
||||
use bevy::{input::mouse::MouseMotion, prelude::*};
|
||||
|
||||
fn main() {
|
||||
println!(
|
||||
"Controls:
|
||||
WSAD - forward/back/strafe left/right
|
||||
LShift - 'run'
|
||||
E - up
|
||||
Q - down
|
||||
L - switch between directional and point lights
|
||||
1/2 - decrease/increase point light depth bias
|
||||
3/4 - decrease/increase point light normal bias
|
||||
5/6 - decrease/increase direction light depth bias
|
||||
7/8 - decrease/increase direction light normal bias"
|
||||
);
|
||||
App::new()
|
||||
.add_plugins(DefaultPlugins)
|
||||
.add_systems(Startup, setup)
|
||||
@ -55,8 +43,6 @@ fn setup(
|
||||
.unwrap(),
|
||||
);
|
||||
|
||||
println!("Using DirectionalLight");
|
||||
|
||||
commands.spawn(PointLightBundle {
|
||||
transform: Transform::from_xyz(5.0, 5.0, 0.0),
|
||||
point_light: PointLight {
|
||||
@ -113,17 +99,57 @@ fn setup(
|
||||
material: white_handle,
|
||||
..default()
|
||||
});
|
||||
|
||||
let style = TextStyle {
|
||||
font_size: 20.,
|
||||
..default()
|
||||
};
|
||||
commands.spawn(
|
||||
TextBundle::from_sections([
|
||||
TextSection::new("Controls:\n", style.clone()),
|
||||
TextSection::new("WSAD - forward/back/strafe left/right\n", style.clone()),
|
||||
TextSection::new("E / Q - up / down\n", style.clone()),
|
||||
TextSection::new(
|
||||
"L - switch between directional and point lights [",
|
||||
style.clone(),
|
||||
),
|
||||
TextSection::new("DirectionalLight", style.clone()),
|
||||
TextSection::new("]\n", style.clone()),
|
||||
TextSection::new("1/2 - change point light depth bias [", style.clone()),
|
||||
TextSection::new("0.00", style.clone()),
|
||||
TextSection::new("]\n", style.clone()),
|
||||
TextSection::new("3/4 - change point light normal bias [", style.clone()),
|
||||
TextSection::new("0.0", style.clone()),
|
||||
TextSection::new("]\n", style.clone()),
|
||||
TextSection::new("5/6 - change direction light depth bias [", style.clone()),
|
||||
TextSection::new("0.00", style.clone()),
|
||||
TextSection::new("]\n", style.clone()),
|
||||
TextSection::new(
|
||||
"7/8 - change direction light normal bias [",
|
||||
style.clone(),
|
||||
),
|
||||
TextSection::new("0.0", style.clone()),
|
||||
TextSection::new("]", style),
|
||||
])
|
||||
.with_style(Style {
|
||||
position_type: PositionType::Absolute,
|
||||
top: Val::Px(12.0),
|
||||
left: Val::Px(12.0),
|
||||
..default()
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
fn toggle_light(
|
||||
input: Res<Input<KeyCode>>,
|
||||
mut point_lights: Query<&mut PointLight>,
|
||||
mut directional_lights: Query<&mut DirectionalLight>,
|
||||
mut example_text: Query<&mut Text>,
|
||||
) {
|
||||
if input.just_pressed(KeyCode::L) {
|
||||
for mut light in &mut point_lights {
|
||||
light.intensity = if light.intensity == 0.0 {
|
||||
println!("Using PointLight");
|
||||
example_text.single_mut().sections[4].value = "PointLight".to_string();
|
||||
100000000.0
|
||||
} else {
|
||||
0.0
|
||||
@ -131,7 +157,7 @@ fn toggle_light(
|
||||
}
|
||||
for mut light in &mut directional_lights {
|
||||
light.illuminance = if light.illuminance == 0.0 {
|
||||
println!("Using DirectionalLight");
|
||||
example_text.single_mut().sections[4].value = "DirectionalLight".to_string();
|
||||
100000.0
|
||||
} else {
|
||||
0.0
|
||||
@ -140,31 +166,31 @@ fn toggle_light(
|
||||
}
|
||||
}
|
||||
|
||||
fn adjust_point_light_biases(input: Res<Input<KeyCode>>, mut query: Query<&mut PointLight>) {
|
||||
fn adjust_point_light_biases(
|
||||
input: Res<Input<KeyCode>>,
|
||||
mut query: Query<&mut PointLight>,
|
||||
mut example_text: Query<&mut Text>,
|
||||
) {
|
||||
let depth_bias_step_size = 0.01;
|
||||
let normal_bias_step_size = 0.1;
|
||||
for mut light in &mut query {
|
||||
if input.just_pressed(KeyCode::Key1) {
|
||||
light.shadow_depth_bias -= depth_bias_step_size;
|
||||
println!("PointLight shadow_depth_bias: {}", light.shadow_depth_bias);
|
||||
example_text.single_mut().sections[7].value = format!("{:.2}", light.shadow_depth_bias);
|
||||
}
|
||||
if input.just_pressed(KeyCode::Key2) {
|
||||
light.shadow_depth_bias += depth_bias_step_size;
|
||||
println!("PointLight shadow_depth_bias: {}", light.shadow_depth_bias);
|
||||
example_text.single_mut().sections[7].value = format!("{:.2}", light.shadow_depth_bias);
|
||||
}
|
||||
if input.just_pressed(KeyCode::Key3) {
|
||||
light.shadow_normal_bias -= normal_bias_step_size;
|
||||
println!(
|
||||
"PointLight shadow_normal_bias: {}",
|
||||
light.shadow_normal_bias
|
||||
);
|
||||
example_text.single_mut().sections[10].value =
|
||||
format!("{:.1}", light.shadow_normal_bias);
|
||||
}
|
||||
if input.just_pressed(KeyCode::Key4) {
|
||||
light.shadow_normal_bias += normal_bias_step_size;
|
||||
println!(
|
||||
"PointLight shadow_normal_bias: {}",
|
||||
light.shadow_normal_bias
|
||||
);
|
||||
example_text.single_mut().sections[10].value =
|
||||
format!("{:.1}", light.shadow_normal_bias);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -172,37 +198,30 @@ fn adjust_point_light_biases(input: Res<Input<KeyCode>>, mut query: Query<&mut P
|
||||
fn adjust_directional_light_biases(
|
||||
input: Res<Input<KeyCode>>,
|
||||
mut query: Query<&mut DirectionalLight>,
|
||||
mut example_text: Query<&mut Text>,
|
||||
) {
|
||||
let depth_bias_step_size = 0.01;
|
||||
let normal_bias_step_size = 0.1;
|
||||
for mut light in &mut query {
|
||||
if input.just_pressed(KeyCode::Key5) {
|
||||
light.shadow_depth_bias -= depth_bias_step_size;
|
||||
println!(
|
||||
"DirectionalLight shadow_depth_bias: {}",
|
||||
light.shadow_depth_bias
|
||||
);
|
||||
example_text.single_mut().sections[13].value =
|
||||
format!("{:.2}", light.shadow_depth_bias);
|
||||
}
|
||||
if input.just_pressed(KeyCode::Key6) {
|
||||
light.shadow_depth_bias += depth_bias_step_size;
|
||||
println!(
|
||||
"DirectionalLight shadow_depth_bias: {}",
|
||||
light.shadow_depth_bias
|
||||
);
|
||||
example_text.single_mut().sections[13].value =
|
||||
format!("{:.2}", light.shadow_depth_bias);
|
||||
}
|
||||
if input.just_pressed(KeyCode::Key7) {
|
||||
light.shadow_normal_bias -= normal_bias_step_size;
|
||||
println!(
|
||||
"DirectionalLight shadow_normal_bias: {}",
|
||||
light.shadow_normal_bias
|
||||
);
|
||||
example_text.single_mut().sections[16].value =
|
||||
format!("{:.1}", light.shadow_normal_bias);
|
||||
}
|
||||
if input.just_pressed(KeyCode::Key8) {
|
||||
light.shadow_normal_bias += normal_bias_step_size;
|
||||
println!(
|
||||
"DirectionalLight shadow_normal_bias: {}",
|
||||
light.shadow_normal_bias
|
||||
);
|
||||
example_text.single_mut().sections[16].value =
|
||||
format!("{:.1}", light.shadow_normal_bias);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -2,6 +2,9 @@
|
||||
//!
|
||||
//! Creates a `Text` with a single `TextSection` containing `100_000` glyphs,
|
||||
//! and renders it with the UI in a white color and with Text2d in a red color.
|
||||
//!
|
||||
//! To recompute all text each frame run
|
||||
//! `cargo run --example many_glyphs --release recompute-text`
|
||||
use bevy::{
|
||||
diagnostic::{FrameTimeDiagnosticsPlugin, LogDiagnosticsPlugin},
|
||||
prelude::*,
|
||||
@ -10,18 +13,23 @@ use bevy::{
|
||||
};
|
||||
|
||||
fn main() {
|
||||
App::new()
|
||||
.add_plugins(DefaultPlugins.set(WindowPlugin {
|
||||
primary_window: Some(Window {
|
||||
present_mode: PresentMode::Immediate,
|
||||
..default()
|
||||
}),
|
||||
let mut app = App::new();
|
||||
app.add_plugins(DefaultPlugins.set(WindowPlugin {
|
||||
primary_window: Some(Window {
|
||||
present_mode: PresentMode::Immediate,
|
||||
..default()
|
||||
}))
|
||||
.add_plugin(FrameTimeDiagnosticsPlugin::default())
|
||||
.add_plugin(LogDiagnosticsPlugin::default())
|
||||
.add_systems(Startup, setup)
|
||||
.run();
|
||||
}),
|
||||
..default()
|
||||
}))
|
||||
.add_plugin(FrameTimeDiagnosticsPlugin::default())
|
||||
.add_plugin(LogDiagnosticsPlugin::default())
|
||||
.add_systems(Startup, setup);
|
||||
|
||||
if std::env::args().any(|arg| arg == "recompute-text") {
|
||||
app.add_systems(Update, force_text_recomputation);
|
||||
}
|
||||
|
||||
app.run();
|
||||
}
|
||||
|
||||
fn setup(mut commands: Commands) {
|
||||
@ -73,3 +81,9 @@ fn setup(mut commands: Commands) {
|
||||
..Default::default()
|
||||
});
|
||||
}
|
||||
|
||||
fn force_text_recomputation(mut text_query: Query<&mut Text>) {
|
||||
for mut text in &mut text_query {
|
||||
text.set_changed();
|
||||
}
|
||||
}
|
||||
|
||||
@ -89,13 +89,11 @@ fn spawn(mut commands: Commands, asset_server: Res<AssetServer>) {
|
||||
linebreak_behavior,
|
||||
};
|
||||
let text_id = commands
|
||||
.spawn((
|
||||
TextBundle {
|
||||
text,
|
||||
..Default::default()
|
||||
},
|
||||
BackgroundColor(Color::rgb(0.8 - j as f32 * 0.2, 0., 0.)),
|
||||
))
|
||||
.spawn(TextBundle {
|
||||
text,
|
||||
background_color: Color::rgb(0.8 - j as f32 * 0.2, 0., 0.).into(),
|
||||
..Default::default()
|
||||
})
|
||||
.id();
|
||||
commands.entity(column_id).add_child(text_id);
|
||||
}
|
||||
|
||||
@ -8,17 +8,17 @@ fn main() {
|
||||
App::new()
|
||||
.add_plugins(DefaultPlugins)
|
||||
.add_systems(Startup, setup)
|
||||
.add_systems(Update, screenshot_on_f12)
|
||||
.add_systems(Update, screenshot_on_spacebar)
|
||||
.run();
|
||||
}
|
||||
|
||||
fn screenshot_on_f12(
|
||||
fn screenshot_on_spacebar(
|
||||
input: Res<Input<KeyCode>>,
|
||||
main_window: Query<Entity, With<PrimaryWindow>>,
|
||||
mut screenshot_manager: ResMut<ScreenshotManager>,
|
||||
mut counter: Local<u32>,
|
||||
) {
|
||||
if input.just_pressed(KeyCode::F12) {
|
||||
if input.just_pressed(KeyCode::Space) {
|
||||
let path = format!("./screenshot-{}.png", *counter);
|
||||
*counter += 1;
|
||||
screenshot_manager
|
||||
@ -61,4 +61,21 @@ fn setup(
|
||||
transform: Transform::from_xyz(-2.0, 2.5, 5.0).looking_at(Vec3::ZERO, Vec3::Y),
|
||||
..default()
|
||||
});
|
||||
|
||||
commands.spawn(
|
||||
TextBundle::from_section(
|
||||
"Press <spacebar> to save a screenshot to disk",
|
||||
TextStyle {
|
||||
font_size: 25.0,
|
||||
color: Color::WHITE,
|
||||
..default()
|
||||
},
|
||||
)
|
||||
.with_style(Style {
|
||||
position_type: PositionType::Absolute,
|
||||
top: Val::Px(10.0),
|
||||
left: Val::Px(10.0),
|
||||
..default()
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user