Merge branch 'main' into meshlet-implicit-tangents

This commit is contained in:
JMS55 2024-09-14 11:48:06 -07:00 committed by GitHub
commit 5a7df13753
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
16 changed files with 271 additions and 199 deletions

View file

@ -3266,7 +3266,6 @@ description = "Shows how to orbit a static scene using pitch, yaw, and roll."
category = "Camera"
wasm = true
[package.metadata.example.fps_overlay]
name = "FPS overlay"
description = "Demonstrates FPS overlay"

View file

@ -21,6 +21,7 @@ use bevy_ecs::{
schedule::SystemSet,
system::Resource,
};
use bevy_reflect::std_traits::ReflectDefault;
use bevy_reflect::Reflect;
/// Wrapper struct for [`accesskit::ActionRequest`]. Required to allow it to be used as an `Event`.
@ -94,7 +95,7 @@ impl From<NodeBuilder> for AccessibilityNode {
/// Resource representing which entity has keyboard focus, if any.
#[derive(Resource, Default, Deref, DerefMut, Reflect)]
#[reflect(Resource)]
#[reflect(Resource, Default)]
pub struct Focus(pub Option<Entity>);
/// Set enum for the systems relating to accessibility

View file

@ -51,8 +51,7 @@ impl FileAssetReader {
/// Returns the base path of the assets directory, which is normally the executable's parent
/// directory.
///
/// If the `CARGO_MANIFEST_DIR` environment variable is set, then its value will be used
/// instead. It's set by cargo when running with `cargo run`.
/// To change this, set [`AssetPlugin.file_path`].
pub fn get_base_path() -> PathBuf {
get_base_path()
}

View file

@ -766,7 +766,7 @@ pub struct Archetypes {
/// find the archetype id by the archetype's components
by_components: HashMap<ArchetypeComponents, ArchetypeId>,
/// find all the archetypes that contain a component
by_component: ComponentIndex,
pub(crate) by_component: ComponentIndex,
}
/// Metadata about how a component is stored in an [`Archetype`].

View file

@ -456,14 +456,17 @@ impl World {
if observers.map.is_empty() && observers.entity_map.is_empty() {
cache.component_observers.remove(component);
if let Some(flag) = Observers::is_archetype_cached(event_type) {
for archetype in &mut archetypes.archetypes {
if archetype.contains(*component) {
let no_longer_observed = archetype
.components()
.all(|id| !cache.component_observers.contains_key(&id));
if let Some(by_component) = archetypes.by_component.get(component) {
for archetype in by_component.keys() {
let archetype = &mut archetypes.archetypes[archetype.index()];
if archetype.contains(*component) {
let no_longer_observed = archetype
.components()
.all(|id| !cache.component_observers.contains_key(&id));
if no_longer_observed {
archetype.flags.set(flag, false);
if no_longer_observed {
archetype.flags.set(flag, false);
}
}
}
}
@ -477,6 +480,8 @@ impl World {
#[cfg(test)]
mod tests {
use std::vec;
use bevy_ptr::OwningPtr;
use crate as bevy_ecs;
@ -503,13 +508,12 @@ mod tests {
struct EventA;
#[derive(Resource, Default)]
struct R(usize);
struct Order(Vec<&'static str>);
impl R {
impl Order {
#[track_caller]
fn assert_order(&mut self, count: usize) {
assert_eq!(count, self.0);
self.0 += 1;
fn observed(&mut self, name: &'static str) {
self.0.push(name);
}
}
@ -534,61 +538,72 @@ mod tests {
#[test]
fn observer_order_spawn_despawn() {
let mut world = World::new();
world.init_resource::<R>();
world.init_resource::<Order>();
world.observe(|_: Trigger<OnAdd, A>, mut res: ResMut<R>| res.assert_order(0));
world.observe(|_: Trigger<OnInsert, A>, mut res: ResMut<R>| res.assert_order(1));
world.observe(|_: Trigger<OnReplace, A>, mut res: ResMut<R>| res.assert_order(2));
world.observe(|_: Trigger<OnRemove, A>, mut res: ResMut<R>| res.assert_order(3));
world.observe(|_: Trigger<OnAdd, A>, mut res: ResMut<Order>| res.observed("add"));
world.observe(|_: Trigger<OnInsert, A>, mut res: ResMut<Order>| res.observed("insert"));
world.observe(|_: Trigger<OnReplace, A>, mut res: ResMut<Order>| res.observed("replace"));
world.observe(|_: Trigger<OnRemove, A>, mut res: ResMut<Order>| res.observed("remove"));
let entity = world.spawn(A).id();
world.despawn(entity);
assert_eq!(4, world.resource::<R>().0);
assert_eq!(
vec!["add", "insert", "replace", "remove"],
world.resource::<Order>().0
);
}
#[test]
fn observer_order_insert_remove() {
let mut world = World::new();
world.init_resource::<R>();
world.init_resource::<Order>();
world.observe(|_: Trigger<OnAdd, A>, mut res: ResMut<R>| res.assert_order(0));
world.observe(|_: Trigger<OnInsert, A>, mut res: ResMut<R>| res.assert_order(1));
world.observe(|_: Trigger<OnReplace, A>, mut res: ResMut<R>| res.assert_order(2));
world.observe(|_: Trigger<OnRemove, A>, mut res: ResMut<R>| res.assert_order(3));
world.observe(|_: Trigger<OnAdd, A>, mut res: ResMut<Order>| res.observed("add"));
world.observe(|_: Trigger<OnInsert, A>, mut res: ResMut<Order>| res.observed("insert"));
world.observe(|_: Trigger<OnReplace, A>, mut res: ResMut<Order>| res.observed("replace"));
world.observe(|_: Trigger<OnRemove, A>, mut res: ResMut<Order>| res.observed("remove"));
let mut entity = world.spawn_empty();
entity.insert(A);
entity.remove::<A>();
entity.flush();
assert_eq!(4, world.resource::<R>().0);
assert_eq!(
vec!["add", "insert", "replace", "remove"],
world.resource::<Order>().0
);
}
#[test]
fn observer_order_insert_remove_sparse() {
let mut world = World::new();
world.init_resource::<R>();
world.init_resource::<Order>();
world.observe(|_: Trigger<OnAdd, S>, mut res: ResMut<R>| res.assert_order(0));
world.observe(|_: Trigger<OnInsert, S>, mut res: ResMut<R>| res.assert_order(1));
world.observe(|_: Trigger<OnReplace, S>, mut res: ResMut<R>| res.assert_order(2));
world.observe(|_: Trigger<OnRemove, S>, mut res: ResMut<R>| res.assert_order(3));
world.observe(|_: Trigger<OnAdd, S>, mut res: ResMut<Order>| res.observed("add"));
world.observe(|_: Trigger<OnInsert, S>, mut res: ResMut<Order>| res.observed("insert"));
world.observe(|_: Trigger<OnReplace, S>, mut res: ResMut<Order>| res.observed("replace"));
world.observe(|_: Trigger<OnRemove, S>, mut res: ResMut<Order>| res.observed("remove"));
let mut entity = world.spawn_empty();
entity.insert(S);
entity.remove::<S>();
entity.flush();
assert_eq!(4, world.resource::<R>().0);
assert_eq!(
vec!["add", "insert", "replace", "remove"],
world.resource::<Order>().0
);
}
#[test]
fn observer_order_replace() {
let mut world = World::new();
world.init_resource::<R>();
world.init_resource::<Order>();
let entity = world.spawn(A).id();
world.observe(|_: Trigger<OnReplace, A>, mut res: ResMut<R>| res.assert_order(0));
world.observe(|_: Trigger<OnInsert, A>, mut res: ResMut<R>| res.assert_order(1));
world.observe(|_: Trigger<OnAdd, A>, mut res: ResMut<Order>| res.observed("add"));
world.observe(|_: Trigger<OnInsert, A>, mut res: ResMut<Order>| res.observed("insert"));
world.observe(|_: Trigger<OnReplace, A>, mut res: ResMut<Order>| res.observed("replace"));
world.observe(|_: Trigger<OnRemove, A>, mut res: ResMut<Order>| res.observed("remove"));
// TODO: ideally this flush is not necessary, but right now observe() returns WorldEntityMut
// and therefore does not automatically flush.
@ -597,53 +612,56 @@ mod tests {
let mut entity = world.entity_mut(entity);
entity.insert(A);
entity.flush();
assert_eq!(2, world.resource::<R>().0);
assert_eq!(vec!["replace", "insert"], world.resource::<Order>().0);
}
#[test]
fn observer_order_recursive() {
let mut world = World::new();
world.init_resource::<R>();
world.init_resource::<Order>();
world.observe(
|obs: Trigger<OnAdd, A>, mut res: ResMut<R>, mut commands: Commands| {
res.assert_order(0);
|obs: Trigger<OnAdd, A>, mut res: ResMut<Order>, mut commands: Commands| {
res.observed("add_a");
commands.entity(obs.entity()).insert(B);
},
);
world.observe(
|obs: Trigger<OnRemove, A>, mut res: ResMut<R>, mut commands: Commands| {
res.assert_order(2);
|obs: Trigger<OnRemove, A>, mut res: ResMut<Order>, mut commands: Commands| {
res.observed("remove_a");
commands.entity(obs.entity()).remove::<B>();
},
);
world.observe(
|obs: Trigger<OnAdd, B>, mut res: ResMut<R>, mut commands: Commands| {
res.assert_order(1);
|obs: Trigger<OnAdd, B>, mut res: ResMut<Order>, mut commands: Commands| {
res.observed("add_b");
commands.entity(obs.entity()).remove::<A>();
},
);
world.observe(|_: Trigger<OnRemove, B>, mut res: ResMut<R>| {
res.assert_order(3);
world.observe(|_: Trigger<OnRemove, B>, mut res: ResMut<Order>| {
res.observed("remove_b");
});
let entity = world.spawn(A).flush();
let entity = world.get_entity(entity).unwrap();
assert!(!entity.contains::<A>());
assert!(!entity.contains::<B>());
assert_eq!(4, world.resource::<R>().0);
assert_eq!(
vec!["add_a", "add_b", "remove_a", "remove_b"],
world.resource::<Order>().0
);
}
#[test]
fn observer_multiple_listeners() {
let mut world = World::new();
world.init_resource::<R>();
world.init_resource::<Order>();
world.observe(|_: Trigger<OnAdd, A>, mut res: ResMut<R>| res.0 += 1);
world.observe(|_: Trigger<OnAdd, A>, mut res: ResMut<R>| res.0 += 1);
world.observe(|_: Trigger<OnAdd, A>, mut res: ResMut<Order>| res.observed("add_1"));
world.observe(|_: Trigger<OnAdd, A>, mut res: ResMut<Order>| res.observed("add_2"));
world.spawn(A).flush();
assert_eq!(2, world.resource::<R>().0);
assert_eq!(vec!["add_1", "add_2"], world.resource::<Order>().0);
// Our A entity plus our two observers
assert_eq!(world.entities().len(), 3);
}
@ -651,40 +669,44 @@ mod tests {
#[test]
fn observer_multiple_events() {
let mut world = World::new();
world.init_resource::<R>();
world.init_resource::<Order>();
let on_remove = world.init_component::<OnRemove>();
world.spawn(
// SAFETY: OnAdd and OnRemove are both unit types, so this is safe
unsafe {
Observer::new(|_: Trigger<OnAdd, A>, mut res: ResMut<R>| res.0 += 1)
.with_event(on_remove)
Observer::new(|_: Trigger<OnAdd, A>, mut res: ResMut<Order>| {
res.observed("add/remove");
})
.with_event(on_remove)
},
);
let entity = world.spawn(A).id();
world.despawn(entity);
assert_eq!(2, world.resource::<R>().0);
assert_eq!(
vec!["add/remove", "add/remove"],
world.resource::<Order>().0
);
}
#[test]
fn observer_multiple_components() {
let mut world = World::new();
world.init_resource::<R>();
world.init_resource::<Order>();
world.init_component::<A>();
world.init_component::<B>();
world.observe(|_: Trigger<OnAdd, (A, B)>, mut res: ResMut<R>| res.0 += 1);
world.observe(|_: Trigger<OnAdd, (A, B)>, mut res: ResMut<Order>| res.observed("add_ab"));
let entity = world.spawn(A).id();
world.entity_mut(entity).insert(B);
world.flush();
assert_eq!(2, world.resource::<R>().0);
assert_eq!(vec!["add_ab", "add_ab"], world.resource::<Order>().0);
}
#[test]
fn observer_despawn() {
let mut world = World::new();
world.init_resource::<R>();
let observer = world
.observe(|_: Trigger<OnAdd, A>| panic!("Observer triggered after being despawned."))
@ -697,11 +719,11 @@ mod tests {
#[test]
fn observer_despawn_archetype_flags() {
let mut world = World::new();
world.init_resource::<R>();
world.init_resource::<Order>();
let entity = world.spawn((A, B)).flush();
world.observe(|_: Trigger<OnRemove, A>, mut res: ResMut<R>| res.0 += 1);
world.observe(|_: Trigger<OnRemove, A>, mut res: ResMut<Order>| res.observed("remove_a"));
let observer = world
.observe(|_: Trigger<OnRemove, B>| panic!("Observer triggered after being despawned."))
@ -710,31 +732,31 @@ mod tests {
world.despawn(entity);
assert_eq!(1, world.resource::<R>().0);
assert_eq!(vec!["remove_a"], world.resource::<Order>().0);
}
#[test]
fn observer_multiple_matches() {
let mut world = World::new();
world.init_resource::<R>();
world.init_resource::<Order>();
world.observe(|_: Trigger<OnAdd, (A, B)>, mut res: ResMut<R>| res.0 += 1);
world.observe(|_: Trigger<OnAdd, (A, B)>, mut res: ResMut<Order>| res.observed("add_ab"));
world.spawn((A, B)).flush();
assert_eq!(1, world.resource::<R>().0);
assert_eq!(vec!["add_ab"], world.resource::<Order>().0);
}
#[test]
fn observer_no_target() {
let mut world = World::new();
world.init_resource::<R>();
world.init_resource::<Order>();
world
.spawn_empty()
.observe(|_: Trigger<EventA>| panic!("Trigger routed to non-targeted entity."));
world.observe(move |obs: Trigger<EventA>, mut res: ResMut<R>| {
world.observe(move |obs: Trigger<EventA>, mut res: ResMut<Order>| {
assert_eq!(obs.entity(), Entity::PLACEHOLDER);
res.0 += 1;
res.observed("event_a");
});
// TODO: ideally this flush is not necessary, but right now observe() returns WorldEntityMut
@ -742,24 +764,24 @@ mod tests {
world.flush();
world.trigger(EventA);
world.flush();
assert_eq!(1, world.resource::<R>().0);
assert_eq!(vec!["event_a"], world.resource::<Order>().0);
}
#[test]
fn observer_entity_routing() {
let mut world = World::new();
world.init_resource::<R>();
world.init_resource::<Order>();
world
.spawn_empty()
.observe(|_: Trigger<EventA>| panic!("Trigger routed to non-targeted entity."));
let entity = world
.spawn_empty()
.observe(|_: Trigger<EventA>, mut res: ResMut<R>| res.0 += 1)
.observe(|_: Trigger<EventA>, mut res: ResMut<Order>| res.observed("a_1"))
.id();
world.observe(move |obs: Trigger<EventA>, mut res: ResMut<R>| {
world.observe(move |obs: Trigger<EventA>, mut res: ResMut<Order>| {
assert_eq!(obs.entity(), entity);
res.0 += 1;
res.observed("a_2");
});
// TODO: ideally this flush is not necessary, but right now observe() returns WorldEntityMut
@ -767,17 +789,17 @@ mod tests {
world.flush();
world.trigger_targets(EventA, entity);
world.flush();
assert_eq!(2, world.resource::<R>().0);
assert_eq!(vec!["a_2", "a_1"], world.resource::<Order>().0);
}
#[test]
fn observer_dynamic_component() {
let mut world = World::new();
world.init_resource::<R>();
world.init_resource::<Order>();
let component_id = world.init_component::<A>();
world.spawn(
Observer::new(|_: Trigger<OnAdd>, mut res: ResMut<R>| res.0 += 1)
Observer::new(|_: Trigger<OnAdd>, mut res: ResMut<Order>| res.observed("event_a"))
.with_component(component_id),
);
@ -790,20 +812,20 @@ mod tests {
world.trigger_targets(EventA, entity);
world.flush();
assert_eq!(1, world.resource::<R>().0);
assert_eq!(vec!["event_a"], world.resource::<Order>().0);
}
#[test]
fn observer_dynamic_trigger() {
let mut world = World::new();
world.init_resource::<R>();
world.init_resource::<Order>();
let event_a = world.init_component::<EventA>();
world.spawn(ObserverState {
// SAFETY: we registered `event_a` above and it matches the type of TriggerA
descriptor: unsafe { ObserverDescriptor::default().with_events(vec![event_a]) },
runner: |mut world, _trigger, _ptr, _propagate| {
world.resource_mut::<R>().0 += 1;
world.resource_mut::<Order>().observed("event_a");
},
..Default::default()
});
@ -813,22 +835,22 @@ mod tests {
unsafe { EmitDynamicTrigger::new_with_id(event_a, EventA, ()) },
);
world.flush();
assert_eq!(1, world.resource::<R>().0);
assert_eq!(vec!["event_a"], world.resource::<Order>().0);
}
#[test]
fn observer_propagating() {
let mut world = World::new();
world.init_resource::<R>();
world.init_resource::<Order>();
let parent = world
.spawn_empty()
.observe(|_: Trigger<EventPropagating>, mut res: ResMut<R>| res.0 += 1)
.observe(|_: Trigger<EventPropagating>, mut res: ResMut<Order>| res.observed("parent"))
.id();
let child = world
.spawn(Parent(parent))
.observe(|_: Trigger<EventPropagating>, mut res: ResMut<R>| res.0 += 1)
.observe(|_: Trigger<EventPropagating>, mut res: ResMut<Order>| res.observed("child"))
.id();
// TODO: ideally this flush is not necessary, but right now observe() returns WorldEntityMut
@ -836,22 +858,22 @@ mod tests {
world.flush();
world.trigger_targets(EventPropagating, child);
world.flush();
assert_eq!(2, world.resource::<R>().0);
assert_eq!(vec!["child", "parent"], world.resource::<Order>().0);
}
#[test]
fn observer_propagating_redundant_dispatch_same_entity() {
let mut world = World::new();
world.init_resource::<R>();
world.init_resource::<Order>();
let parent = world
.spawn_empty()
.observe(|_: Trigger<EventPropagating>, mut res: ResMut<R>| res.0 += 1)
.observe(|_: Trigger<EventPropagating>, mut res: ResMut<Order>| res.observed("parent"))
.id();
let child = world
.spawn(Parent(parent))
.observe(|_: Trigger<EventPropagating>, mut res: ResMut<R>| res.0 += 1)
.observe(|_: Trigger<EventPropagating>, mut res: ResMut<Order>| res.observed("child"))
.id();
// TODO: ideally this flush is not necessary, but right now observe() returns WorldEntityMut
@ -859,22 +881,25 @@ mod tests {
world.flush();
world.trigger_targets(EventPropagating, [child, child]);
world.flush();
assert_eq!(4, world.resource::<R>().0);
assert_eq!(
vec!["child", "parent", "child", "parent"],
world.resource::<Order>().0
);
}
#[test]
fn observer_propagating_redundant_dispatch_parent_child() {
let mut world = World::new();
world.init_resource::<R>();
world.init_resource::<Order>();
let parent = world
.spawn_empty()
.observe(|_: Trigger<EventPropagating>, mut res: ResMut<R>| res.0 += 1)
.observe(|_: Trigger<EventPropagating>, mut res: ResMut<Order>| res.observed("parent"))
.id();
let child = world
.spawn(Parent(parent))
.observe(|_: Trigger<EventPropagating>, mut res: ResMut<R>| res.0 += 1)
.observe(|_: Trigger<EventPropagating>, mut res: ResMut<Order>| res.observed("child"))
.id();
// TODO: ideally this flush is not necessary, but right now observe() returns WorldEntityMut
@ -882,24 +907,27 @@ mod tests {
world.flush();
world.trigger_targets(EventPropagating, [child, parent]);
world.flush();
assert_eq!(3, world.resource::<R>().0);
assert_eq!(
vec!["child", "parent", "parent"],
world.resource::<Order>().0
);
}
#[test]
fn observer_propagating_halt() {
let mut world = World::new();
world.init_resource::<R>();
world.init_resource::<Order>();
let parent = world
.spawn_empty()
.observe(|_: Trigger<EventPropagating>, mut res: ResMut<R>| res.0 += 1)
.observe(|_: Trigger<EventPropagating>, mut res: ResMut<Order>| res.observed("parent"))
.id();
let child = world
.spawn(Parent(parent))
.observe(
|mut trigger: Trigger<EventPropagating>, mut res: ResMut<R>| {
res.0 += 1;
|mut trigger: Trigger<EventPropagating>, mut res: ResMut<Order>| {
res.observed("child");
trigger.propagate(false);
},
)
@ -910,30 +938,30 @@ mod tests {
world.flush();
world.trigger_targets(EventPropagating, child);
world.flush();
assert_eq!(1, world.resource::<R>().0);
assert_eq!(vec!["child"], world.resource::<Order>().0);
}
#[test]
fn observer_propagating_join() {
let mut world = World::new();
world.init_resource::<R>();
world.init_resource::<Order>();
let parent = world
.spawn_empty()
.observe(|_: Trigger<EventPropagating>, mut res: ResMut<R>| res.0 += 1)
.observe(|_: Trigger<EventPropagating>, mut res: ResMut<Order>| res.observed("parent"))
.id();
let child_a = world
.spawn(Parent(parent))
.observe(|_: Trigger<EventPropagating>, mut res: ResMut<R>| {
res.0 += 1;
.observe(|_: Trigger<EventPropagating>, mut res: ResMut<Order>| {
res.observed("child_a");
})
.id();
let child_b = world
.spawn(Parent(parent))
.observe(|_: Trigger<EventPropagating>, mut res: ResMut<R>| {
res.0 += 1;
.observe(|_: Trigger<EventPropagating>, mut res: ResMut<Order>| {
res.observed("child_b");
})
.id();
@ -942,17 +970,20 @@ mod tests {
world.flush();
world.trigger_targets(EventPropagating, [child_a, child_b]);
world.flush();
assert_eq!(4, world.resource::<R>().0);
assert_eq!(
vec!["child_a", "parent", "child_b", "parent"],
world.resource::<Order>().0
);
}
#[test]
fn observer_propagating_no_next() {
let mut world = World::new();
world.init_resource::<R>();
world.init_resource::<Order>();
let entity = world
.spawn_empty()
.observe(|_: Trigger<EventPropagating>, mut res: ResMut<R>| res.0 += 1)
.observe(|_: Trigger<EventPropagating>, mut res: ResMut<Order>| res.observed("event"))
.id();
// TODO: ideally this flush is not necessary, but right now observe() returns WorldEntityMut
@ -960,24 +991,26 @@ mod tests {
world.flush();
world.trigger_targets(EventPropagating, entity);
world.flush();
assert_eq!(1, world.resource::<R>().0);
assert_eq!(vec!["event"], world.resource::<Order>().0);
}
#[test]
fn observer_propagating_parallel_propagation() {
let mut world = World::new();
world.init_resource::<R>();
world.init_resource::<Order>();
let parent_a = world
.spawn_empty()
.observe(|_: Trigger<EventPropagating>, mut res: ResMut<R>| res.0 += 1)
.observe(|_: Trigger<EventPropagating>, mut res: ResMut<Order>| {
res.observed("parent_a");
})
.id();
let child_a = world
.spawn(Parent(parent_a))
.observe(
|mut trigger: Trigger<EventPropagating>, mut res: ResMut<R>| {
res.0 += 1;
|mut trigger: Trigger<EventPropagating>, mut res: ResMut<Order>| {
res.observed("child_a");
trigger.propagate(false);
},
)
@ -985,12 +1018,14 @@ mod tests {
let parent_b = world
.spawn_empty()
.observe(|_: Trigger<EventPropagating>, mut res: ResMut<R>| res.0 += 1)
.observe(|_: Trigger<EventPropagating>, mut res: ResMut<Order>| {
res.observed("parent_b");
})
.id();
let child_b = world
.spawn(Parent(parent_b))
.observe(|_: Trigger<EventPropagating>, mut res: ResMut<R>| res.0 += 1)
.observe(|_: Trigger<EventPropagating>, mut res: ResMut<Order>| res.observed("child_b"))
.id();
// TODO: ideally this flush is not necessary, but right now observe() returns WorldEntityMut
@ -998,15 +1033,18 @@ mod tests {
world.flush();
world.trigger_targets(EventPropagating, [child_a, child_b]);
world.flush();
assert_eq!(3, world.resource::<R>().0);
assert_eq!(
vec!["child_a", "child_b", "parent_b"],
world.resource::<Order>().0
);
}
#[test]
fn observer_propagating_world() {
let mut world = World::new();
world.init_resource::<R>();
world.init_resource::<Order>();
world.observe(|_: Trigger<EventPropagating>, mut res: ResMut<R>| res.0 += 1);
world.observe(|_: Trigger<EventPropagating>, mut res: ResMut<Order>| res.observed("event"));
let grandparent = world.spawn_empty().id();
let parent = world.spawn(Parent(grandparent)).id();
@ -1017,18 +1055,18 @@ mod tests {
world.flush();
world.trigger_targets(EventPropagating, child);
world.flush();
assert_eq!(3, world.resource::<R>().0);
assert_eq!(vec!["event", "event", "event"], world.resource::<Order>().0);
}
#[test]
fn observer_propagating_world_skipping() {
let mut world = World::new();
world.init_resource::<R>();
world.init_resource::<Order>();
world.observe(
|trigger: Trigger<EventPropagating>, query: Query<&A>, mut res: ResMut<R>| {
|trigger: Trigger<EventPropagating>, query: Query<&A>, mut res: ResMut<Order>| {
if query.get(trigger.entity()).is_ok() {
res.0 += 1;
res.observed("event");
}
},
);
@ -1042,6 +1080,6 @@ mod tests {
world.flush();
world.trigger_targets(EventPropagating, child);
world.flush();
assert_eq!(2, world.resource::<R>().0);
assert_eq!(vec!["event", "event"], world.resource::<Order>().0);
}
}

View file

@ -450,7 +450,7 @@ impl ShapeSample for Capsule2d {
if capsule_area > 0.0 {
// Check if the random point should be inside the rectangle
if rng.gen_bool((rectangle_area / capsule_area) as f64) {
let rectangle = Rectangle::new(self.radius, self.half_length * 2.0);
let rectangle = Rectangle::new(self.radius * 2.0, self.half_length * 2.0);
rectangle.sample_interior(rng)
} else {
let circle = Circle::new(self.radius);

View file

@ -1632,7 +1632,7 @@ mod tests {
// TypeInfo (instance)
let value: &dyn Reflect = &123_i32;
let info = value.get_represented_type_info().unwrap();
let info = value.reflect_type_info();
assert!(info.is::<i32>());
// Struct
@ -1653,7 +1653,7 @@ mod tests {
assert_eq!(usize::type_path(), info.field_at(1).unwrap().type_path());
let value: &dyn Reflect = &MyStruct { foo: 123, bar: 321 };
let info = value.get_represented_type_info().unwrap();
let info = value.reflect_type_info();
assert!(info.is::<MyStruct>());
// Struct (generic)
@ -1675,7 +1675,7 @@ mod tests {
foo: String::from("Hello!"),
bar: 321,
};
let info = value.get_represented_type_info().unwrap();
let info = value.reflect_type_info();
assert!(info.is::<MyGenericStruct<String>>());
// Struct (dynamic field)
@ -1705,7 +1705,7 @@ mod tests {
foo: DynamicStruct::default(),
bar: 321,
};
let info = value.get_represented_type_info().unwrap();
let info = value.reflect_type_info();
assert!(info.is::<MyDynamicStruct>());
// Tuple Struct
@ -1731,7 +1731,7 @@ mod tests {
assert!(info.field_at(1).unwrap().type_info().unwrap().is::<f32>());
let value: &dyn Reflect = &(123_u32, 1.23_f32, String::from("Hello!"));
let info = value.get_represented_type_info().unwrap();
let info = value.reflect_type_info();
assert!(info.is::<MyTuple>());
// List
@ -1746,7 +1746,7 @@ mod tests {
assert_eq!(usize::type_path(), info.item_ty().path());
let value: &dyn Reflect = &vec![123_usize];
let info = value.get_represented_type_info().unwrap();
let info = value.reflect_type_info();
assert!(info.is::<MyList>());
// List (SmallVec)
@ -1763,7 +1763,7 @@ mod tests {
let value: MySmallVec = smallvec::smallvec![String::default(); 2];
let value: &dyn Reflect = &value;
let info = value.get_represented_type_info().unwrap();
let info = value.reflect_type_info();
assert!(info.is::<MySmallVec>());
}
@ -1779,7 +1779,7 @@ mod tests {
assert_eq!(3, info.capacity());
let value: &dyn Reflect = &[1usize, 2usize, 3usize];
let info = value.get_represented_type_info().unwrap();
let info = value.reflect_type_info();
assert!(info.is::<MyArray>());
// Cow<'static, str>
@ -1791,7 +1791,7 @@ mod tests {
assert_eq!(std::any::type_name::<MyCowStr>(), info.type_path());
let value: &dyn Reflect = &Cow::<'static, str>::Owned("Hello!".to_string());
let info = value.get_represented_type_info().unwrap();
let info = value.reflect_type_info();
assert!(info.is::<MyCowStr>());
// Cow<'static, [u8]>
@ -1806,7 +1806,7 @@ mod tests {
assert_eq!(std::any::type_name::<u8>(), info.item_ty().path());
let value: &dyn Reflect = &Cow::<'static, [u8]>::Owned(vec![0, 1, 2, 3]);
let info = value.get_represented_type_info().unwrap();
let info = value.reflect_type_info();
assert!(info.is::<MyCowSlice>());
// Map
@ -1824,7 +1824,7 @@ mod tests {
assert_eq!(f32::type_path(), info.value_ty().path());
let value: &dyn Reflect = &MyMap::new();
let info = value.get_represented_type_info().unwrap();
let info = value.reflect_type_info();
assert!(info.is::<MyMap>());
// Value
@ -1836,7 +1836,7 @@ mod tests {
assert_eq!(MyValue::type_path(), info.type_path());
let value: &dyn Reflect = &String::from("Hello!");
let info = value.get_represented_type_info().unwrap();
let info = value.reflect_type_info();
assert!(info.is::<MyValue>());
}

View file

@ -478,6 +478,13 @@ impl<const N: usize> From<[Access<'static>; N]> for ParsedPath {
}
}
impl<'a> TryFrom<&'a str> for ParsedPath {
type Error = ReflectPathError<'a>;
fn try_from(value: &'a str) -> Result<Self, Self::Error> {
ParsedPath::parse(value)
}
}
impl fmt::Display for ParsedPath {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
for OffsetAccess { access, .. } in &self.0 {
@ -585,6 +592,21 @@ mod tests {
})
}
#[test]
fn try_from() {
assert_eq!(
ParsedPath::try_from("w").unwrap().0,
&[offset(access_field("w"), 1)]
);
let r = ParsedPath::try_from("w[");
let matches = matches!(r, Err(ReflectPathError::ParseError { .. }));
assert!(
matches,
"ParsedPath::try_from did not return a ParseError for \"w[\""
);
}
#[test]
fn parsed_path_parse() {
assert_eq!(

View file

@ -1,7 +1,7 @@
use crate::{
array_debug, enum_debug, list_debug, map_debug, serde::Serializable, struct_debug, tuple_debug,
tuple_struct_debug, Array, DynamicTypePath, Enum, List, Map, Set, Struct, Tuple, TupleStruct,
TypeInfo, TypePath, Typed, ValueInfo,
tuple_struct_debug, Array, DynamicTypePath, DynamicTyped, Enum, List, Map, Set, Struct, Tuple,
TupleStruct, TypeInfo, TypePath, Typed, ValueInfo,
};
use std::{
any::{Any, TypeId},
@ -408,7 +408,7 @@ where
message = "`{Self}` does not implement `Reflect` so cannot be fully reflected",
note = "consider annotating `{Self}` with `#[derive(Reflect)]`"
)]
pub trait Reflect: PartialReflect + Any {
pub trait Reflect: PartialReflect + DynamicTyped + Any {
/// Returns the value as a [`Box<dyn Any>`][std::any::Any].
///
/// For remote wrapper types, this will return the remote type instead.

View file

@ -30,12 +30,7 @@ impl<'a> Serializable<'a> {
))
})?;
let info = value.get_represented_type_info().ok_or_else(|| {
make_custom_error(format_args!(
"type `{}` does not represent any type",
value.reflect_type_path(),
))
})?;
let info = value.reflect_type_info();
let registration = type_registry.get(info.type_id()).ok_or_else(|| {
make_custom_error(format_args!(

View file

@ -136,6 +136,27 @@ impl MaybeTyped for DynamicArray {}
impl MaybeTyped for DynamicTuple {}
/// Dynamic dispatch for [`Typed`].
///
/// Since this is a supertrait of [`Reflect`] its methods can be called on a `dyn Reflect`.
///
/// [`Reflect`]: crate::Reflect
#[diagnostic::on_unimplemented(
message = "`{Self}` can not provide dynamic type information through reflection",
note = "consider annotating `{Self}` with `#[derive(Reflect)]`"
)]
pub trait DynamicTyped {
/// See [`Typed::type_info`].
fn reflect_type_info(&self) -> &'static TypeInfo;
}
impl<T: Typed> DynamicTyped for T {
#[inline]
fn reflect_type_info(&self) -> &'static TypeInfo {
Self::type_info()
}
}
/// A [`TypeInfo`]-specific error.
#[derive(Debug, Error)]
pub enum TypeInfoError {

View file

@ -171,7 +171,7 @@ impl<T: TypedProperty> Default for NonGenericTypeCell<T> {
/// # fn reflect_owned(self: Box<Self>) -> ReflectOwned { todo!() }
/// # fn clone_value(&self) -> Box<dyn PartialReflect> { todo!() }
/// # }
/// # impl<T: Reflect + TypePath> Reflect for Foo<T> {
/// # impl<T: Reflect + Typed + TypePath> Reflect for Foo<T> {
/// # 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!() }

View file

@ -260,8 +260,7 @@ pub fn extract_uinode_background_colors(
uinode.border_radius.top_right,
uinode.border_radius.bottom_right,
uinode.border_radius.bottom_left,
]
.map(|r| r * ui_scale.0);
];
extracted_uinodes.uinodes.insert(
entity,
@ -387,8 +386,7 @@ pub fn extract_uinode_images(
uinode.border_radius.top_right,
uinode.border_radius.bottom_right,
uinode.border_radius.bottom_left,
]
.map(|r| r * ui_scale.0);
];
extracted_uinodes.uinodes.insert(
commands.spawn_empty().id(),
@ -525,8 +523,7 @@ pub fn extract_uinode_borders(
uinode.border_radius.top_right,
uinode.border_radius.bottom_right,
uinode.border_radius.bottom_left,
]
.map(|r| r * ui_scale.0);
];
let border_radius = clamp_radius(border_radius, uinode.size(), border.into());

View file

@ -19,10 +19,7 @@ fn setup(
// For example, the next line will load GltfAssetLabel::Primitive{mesh:0,primitive:0}.from_asset("ROOT/assets/models/cube/cube.gltf"),
// where "ROOT" is the directory of the Application.
//
// This can be overridden by setting the "CARGO_MANIFEST_DIR" environment variable (see
// https://doc.rust-lang.org/cargo/reference/environment-variables.html)
// to another directory. When the Application is run through Cargo, "CARGO_MANIFEST_DIR" is
// automatically set to your crate (workspace) root directory.
// This can be overridden by setting [`AssetPlugin.file_path`].
let cube_handle = asset_server.load(
GltfAssetLabel::Primitive {
mesh: 0,

View file

@ -1,16 +1,36 @@
//! Shows how to orbit camera around a static scene using pitch, yaw, and roll.
//!
//! See also: `first_person_view_model` example, which does something similar but as a first-person
//! camera view.
use std::{f32::consts::FRAC_PI_2, ops::Range};
use bevy::prelude::*;
use bevy::{input::mouse::AccumulatedMouseMotion, prelude::*};
#[derive(Debug, Default, Resource)]
#[derive(Debug, Resource)]
struct CameraSettings {
pub orbit_distance: f32,
// Multiply keyboard inputs by this factor
pub orbit_speed: f32,
pub pitch_speed: f32,
// Clamp pitch to this range
pub pitch_range: Range<f32>,
pub roll_speed: f32,
pub yaw_speed: f32,
}
impl Default for CameraSettings {
fn default() -> Self {
// Limiting pitch stops some unexpected rotation past 90° up or down.
let pitch_limit = FRAC_PI_2 - 0.01;
Self {
// These values are completely arbitrary, chosen because they seem to produce
// "sensible" results for this example. Adjust as required.
orbit_distance: 20.0,
pitch_speed: 0.003,
pitch_range: -pitch_limit..pitch_limit,
roll_speed: 1.0,
yaw_speed: 0.004,
}
}
}
fn main() {
@ -24,18 +44,10 @@ fn main() {
/// Set up a simple 3D scene
fn setup(
mut camera_settings: ResMut<CameraSettings>,
mut commands: Commands,
mut meshes: ResMut<Assets<Mesh>>,
mut materials: ResMut<Assets<StandardMaterial>>,
) {
// Limiting pitch stops some unexpected rotation past 90° up or down.
let pitch_limit = FRAC_PI_2 - 0.01;
camera_settings.orbit_distance = 10.0;
camera_settings.orbit_speed = 1.0;
camera_settings.pitch_range = -pitch_limit..pitch_limit;
commands.spawn((
Name::new("Camera"),
Camera3dBundle {
@ -94,15 +106,15 @@ fn instructions(mut commands: Commands) {
))
.with_children(|parent| {
parent.spawn(TextBundle::from_section(
"W or S: pitch",
"Mouse up or down: pitch",
TextStyle::default(),
));
parent.spawn(TextBundle::from_section(
"A or D: yaw",
"Mouse left or right: yaw",
TextStyle::default(),
));
parent.spawn(TextBundle::from_section(
"Q or E: roll",
"Mouse buttons: roll",
TextStyle::default(),
));
});
@ -111,40 +123,29 @@ fn instructions(mut commands: Commands) {
fn orbit(
mut camera: Query<&mut Transform, With<Camera>>,
camera_settings: Res<CameraSettings>,
keyboard_input: Res<ButtonInput<KeyCode>>,
mouse_buttons: Res<ButtonInput<MouseButton>>,
mouse_motion: Res<AccumulatedMouseMotion>,
time: Res<Time>,
) {
let mut transform = camera.single_mut();
let mut delta_pitch = 0.0;
let delta = mouse_motion.delta;
let mut delta_roll = 0.0;
let mut delta_yaw = 0.0;
if keyboard_input.pressed(KeyCode::KeyW) {
delta_pitch += camera_settings.orbit_speed;
if mouse_buttons.pressed(MouseButton::Left) {
delta_roll -= 1.0;
}
if keyboard_input.pressed(KeyCode::KeyS) {
delta_pitch -= camera_settings.orbit_speed;
if mouse_buttons.pressed(MouseButton::Right) {
delta_roll += 1.0;
}
if keyboard_input.pressed(KeyCode::KeyQ) {
delta_roll -= camera_settings.orbit_speed;
}
if keyboard_input.pressed(KeyCode::KeyE) {
delta_roll += camera_settings.orbit_speed;
}
// Mouse motion is one of the few inputs that should not be multiplied by delta time,
// as we are already receiving the full movement since the last frame was rendered. Multiplying
// by delta time here would make the movement slower that it should be.
let delta_pitch = delta.y * camera_settings.pitch_speed;
let delta_yaw = delta.x * camera_settings.yaw_speed;
if keyboard_input.pressed(KeyCode::KeyA) {
delta_yaw -= camera_settings.orbit_speed;
}
if keyboard_input.pressed(KeyCode::KeyD) {
delta_yaw += camera_settings.orbit_speed;
}
// Incorporating the delta time between calls prevents this from being framerate-bound.
delta_pitch *= time.delta_seconds();
delta_roll *= time.delta_seconds();
delta_yaw *= time.delta_seconds();
// Conversely, we DO need to factor in delta time for mouse button inputs.
delta_roll *= camera_settings.roll_speed * time.delta_seconds();
// Obtain the existing pitch, yaw, and roll values from the transform.
let (yaw, pitch, roll) = transform.rotation.to_euler(EulerRot::YXZ);
@ -159,5 +160,7 @@ fn orbit(
transform.rotation = Quat::from_euler(EulerRot::YXZ, yaw, pitch, roll);
// Adjust the translation to maintain the correct orientation toward the orbit target.
transform.translation = Vec3::ZERO - transform.forward() * camera_settings.orbit_distance;
// In our example it's a static target, but this could easily be customised.
let target = Vec3::ZERO;
transform.translation = target - transform.forward() * camera_settings.orbit_distance;
}

View file

@ -80,7 +80,7 @@ fn main() {
registry.register_type_data::<Zombie, ReflectDamageable>();
// Then at any point we can retrieve the type data from the registry:
let type_id = value.get_represented_type_info().unwrap().type_id();
let type_id = value.reflect_type_info().type_id();
let reflect_damageable = registry
.get_type_data::<ReflectDamageable>(type_id)
.unwrap();
@ -133,7 +133,7 @@ fn main() {
// Now we can use `ReflectHealth` to convert `dyn Reflect` into `dyn Health`:
let value: Box<dyn Reflect> = Box::new(Skeleton { health: 50 });
let type_id = value.get_represented_type_info().unwrap().type_id();
let type_id = value.reflect_type_info().type_id();
let reflect_health = registry.get_type_data::<ReflectHealth>(type_id).unwrap();
// Type data generated by `#[reflect_trait]` comes with a `get`, `get_mut`, and `get_boxed` method,