Merge branch 'main' into nuke_get_base_descriptor
This commit is contained in:
commit
88b3b5e71e
11
Cargo.toml
11
Cargo.toml
@ -3068,6 +3068,17 @@ description = "Test rendering of many UI elements"
|
||||
category = "Stress Tests"
|
||||
wasm = true
|
||||
|
||||
[[example]]
|
||||
name = "many_gradients"
|
||||
path = "examples/stress_tests/many_gradients.rs"
|
||||
doc-scrape-examples = true
|
||||
|
||||
[package.metadata.example.many_gradients]
|
||||
name = "Many Gradients"
|
||||
description = "Stress test for gradient rendering performance"
|
||||
category = "Stress Tests"
|
||||
wasm = true
|
||||
|
||||
[[example]]
|
||||
name = "many_cameras_lights"
|
||||
path = "examples/stress_tests/many_cameras_lights.rs"
|
||||
|
@ -12,7 +12,7 @@ struct RegisteredEvent {
|
||||
event_key: EventKey,
|
||||
// Required to flush the secondary buffer and drop events even if left unchanged.
|
||||
previously_updated: bool,
|
||||
// SAFETY: The component ID and the function must be used to fetch the Events<T> resource
|
||||
// SAFETY: The `EventKey`'s component ID and the function must be used to fetch the Events<T> resource
|
||||
// of the same type initialized in `register_event`, or improper type casts will occur.
|
||||
update: unsafe fn(MutUntyped),
|
||||
}
|
||||
|
@ -31,20 +31,16 @@ pub fn signal_event_update_system(signal: Option<ResMut<EventRegistry>>) {
|
||||
|
||||
/// A system that calls [`Events::update`](super::Events::update) on all registered [`Events`][super::Events] in the world.
|
||||
pub fn event_update_system(world: &mut World, mut last_change_tick: Local<Tick>) {
|
||||
if world.contains_resource::<EventRegistry>() {
|
||||
world.resource_scope(|world, mut registry: Mut<EventRegistry>| {
|
||||
registry.run_updates(world, *last_change_tick);
|
||||
world.try_resource_scope(|world, mut registry: Mut<EventRegistry>| {
|
||||
registry.run_updates(world, *last_change_tick);
|
||||
|
||||
registry.should_update = match registry.should_update {
|
||||
// If we're always updating, keep doing so.
|
||||
ShouldUpdateEvents::Always => ShouldUpdateEvents::Always,
|
||||
// Disable the system until signal_event_update_system runs again.
|
||||
ShouldUpdateEvents::Waiting | ShouldUpdateEvents::Ready => {
|
||||
ShouldUpdateEvents::Waiting
|
||||
}
|
||||
};
|
||||
});
|
||||
}
|
||||
registry.should_update = match registry.should_update {
|
||||
// If we're always updating, keep doing so.
|
||||
ShouldUpdateEvents::Always => ShouldUpdateEvents::Always,
|
||||
// Disable the system until signal_event_update_system runs again.
|
||||
ShouldUpdateEvents::Waiting | ShouldUpdateEvents::Ready => ShouldUpdateEvents::Waiting,
|
||||
};
|
||||
});
|
||||
*last_change_tick = world.change_tick();
|
||||
}
|
||||
|
||||
|
@ -248,7 +248,7 @@ impl Observer {
|
||||
world.commands().queue(move |world: &mut World| {
|
||||
let entity = hook_context.entity;
|
||||
if let Some(mut observe) = world.get_mut::<Observer>(entity) {
|
||||
if observe.descriptor.events.is_empty() {
|
||||
if observe.descriptor.event_keys.is_empty() {
|
||||
return;
|
||||
}
|
||||
if observe.error_handler.is_none() {
|
||||
@ -286,13 +286,13 @@ impl Observer {
|
||||
self
|
||||
}
|
||||
|
||||
/// Observe the given `event`. This will cause the [`Observer`] to run whenever an event with the given [`ComponentId`]
|
||||
/// Observe the given `event_key`. This will cause the [`Observer`] to run whenever an event with the given [`EventKey`]
|
||||
/// is triggered.
|
||||
/// # Safety
|
||||
/// The type of the `event` [`EventKey`] _must_ match the actual value
|
||||
/// The type of the `event_key` [`EventKey`] _must_ match the actual value
|
||||
/// of the event passed into the observer system.
|
||||
pub unsafe fn with_event(mut self, event: EventKey) -> Self {
|
||||
self.descriptor.events.push(event);
|
||||
pub unsafe fn with_event_key(mut self, event_key: EventKey) -> Self {
|
||||
self.descriptor.event_keys.push(event_key);
|
||||
self
|
||||
}
|
||||
|
||||
@ -349,8 +349,8 @@ impl Component for Observer {
|
||||
/// This information is stored inside of the [`Observer`] component,
|
||||
#[derive(Default, Clone)]
|
||||
pub struct ObserverDescriptor {
|
||||
/// The events the observer is watching.
|
||||
pub(super) events: Vec<EventKey>,
|
||||
/// The event keys the observer is watching.
|
||||
pub(super) event_keys: Vec<EventKey>,
|
||||
|
||||
/// The components the observer is watching.
|
||||
pub(super) components: Vec<ComponentId>,
|
||||
@ -360,12 +360,12 @@ pub struct ObserverDescriptor {
|
||||
}
|
||||
|
||||
impl ObserverDescriptor {
|
||||
/// Add the given `events` to the descriptor.
|
||||
/// Add the given `event_keys` to the descriptor.
|
||||
/// # Safety
|
||||
/// The type of each [`EventKey`] in `events` _must_ match the actual value
|
||||
/// The type of each [`EventKey`] in `event_keys` _must_ match the actual value
|
||||
/// of the event passed into the observer.
|
||||
pub unsafe fn with_events(mut self, events: Vec<EventKey>) -> Self {
|
||||
self.events = events;
|
||||
pub unsafe fn with_event_keys(mut self, event_keys: Vec<EventKey>) -> Self {
|
||||
self.event_keys = event_keys;
|
||||
self
|
||||
}
|
||||
|
||||
@ -381,9 +381,9 @@ impl ObserverDescriptor {
|
||||
self
|
||||
}
|
||||
|
||||
/// Returns the `events` that the observer is watching.
|
||||
pub fn events(&self) -> &[EventKey] {
|
||||
&self.events
|
||||
/// Returns the `event_keys` that the observer is watching.
|
||||
pub fn event_keys(&self) -> &[EventKey] {
|
||||
&self.event_keys
|
||||
}
|
||||
|
||||
/// Returns the `components` that the observer is watching.
|
||||
@ -416,7 +416,7 @@ fn hook_on_add<E: Event, B: Bundle, S: ObserverSystem<E, B>>(
|
||||
components.push(id);
|
||||
});
|
||||
if let Some(mut observer) = world.get_mut::<Observer>(entity) {
|
||||
observer.descriptor.events.push(event_key);
|
||||
observer.descriptor.event_keys.push(event_key);
|
||||
observer.descriptor.components.extend(components);
|
||||
|
||||
let system: &mut dyn Any = observer.system.as_mut();
|
||||
|
@ -43,7 +43,7 @@ fn component_clone_observed_by(_source: &SourceComponent, ctx: &mut ComponentClo
|
||||
.get_mut::<Observer>(observer_entity)
|
||||
.expect("Source observer entity must have Observer");
|
||||
observer_state.descriptor.entities.push(target);
|
||||
let event_keys = observer_state.descriptor.events.clone();
|
||||
let event_keys = observer_state.descriptor.event_keys.clone();
|
||||
let components = observer_state.descriptor.components.clone();
|
||||
for event_key in event_keys {
|
||||
let observers = world.observers.get_observers_mut(event_key);
|
||||
|
@ -379,7 +379,7 @@ impl World {
|
||||
};
|
||||
let descriptor = &observer_state.descriptor;
|
||||
|
||||
for &event_key in &descriptor.events {
|
||||
for &event_key in &descriptor.event_keys {
|
||||
let cache = observers.get_observers_mut(event_key);
|
||||
|
||||
if descriptor.components.is_empty() && descriptor.entities.is_empty() {
|
||||
@ -430,7 +430,7 @@ impl World {
|
||||
let archetypes = &mut self.archetypes;
|
||||
let observers = &mut self.observers;
|
||||
|
||||
for &event_key in &descriptor.events {
|
||||
for &event_key in &descriptor.event_keys {
|
||||
let cache = observers.get_observers_mut(event_key);
|
||||
if descriptor.components.is_empty() && descriptor.entities.is_empty() {
|
||||
cache.global_observers.remove(&entity);
|
||||
@ -741,7 +741,7 @@ mod tests {
|
||||
Observer::new(|_: On<Add, A>, mut res: ResMut<Order>| {
|
||||
res.observed("add/remove");
|
||||
})
|
||||
.with_event(on_remove)
|
||||
.with_event_key(on_remove)
|
||||
},
|
||||
);
|
||||
|
||||
@ -1015,7 +1015,7 @@ mod tests {
|
||||
Observer::with_dynamic_runner(|mut world, _trigger, _ptr, _propagate| {
|
||||
world.resource_mut::<Order>().observed("event_a");
|
||||
})
|
||||
.with_event(event_a)
|
||||
.with_event_key(event_a)
|
||||
};
|
||||
world.spawn(observe);
|
||||
|
||||
|
@ -181,7 +181,7 @@ impl<'w, E, B: Bundle> DerefMut for On<'w, E, B> {
|
||||
pub struct ObserverTrigger {
|
||||
/// The [`Entity`] of the observer handling the trigger.
|
||||
pub observer: Entity,
|
||||
/// The [`Event`] the trigger targeted.
|
||||
/// The [`EventKey`] the trigger targeted.
|
||||
pub event_key: EventKey,
|
||||
/// The [`ComponentId`]s the trigger targeted.
|
||||
pub components: SmallVec<[ComponentId; 2]>,
|
||||
|
@ -1,10 +1,9 @@
|
||||
use crate::{
|
||||
entity::Entity,
|
||||
prelude::Mut,
|
||||
reflect::{AppTypeRegistry, ReflectBundle, ReflectComponent},
|
||||
resource::Resource,
|
||||
system::EntityCommands,
|
||||
world::{EntityWorldMut, World},
|
||||
world::EntityWorldMut,
|
||||
};
|
||||
use alloc::{borrow::Cow, boxed::Box};
|
||||
use bevy_reflect::{PartialReflect, TypeRegistry};
|
||||
@ -22,7 +21,7 @@ pub trait ReflectCommandExt {
|
||||
/// - If [`AppTypeRegistry`] does not have the reflection data for the given
|
||||
/// [`Component`](crate::component::Component) or [`Bundle`](crate::bundle::Bundle).
|
||||
/// - If the component or bundle data is invalid. See [`PartialReflect::apply`] for further details.
|
||||
/// - If [`AppTypeRegistry`] is not present in the [`World`].
|
||||
/// - If [`AppTypeRegistry`] is not present in the [`World`](crate::world::World).
|
||||
///
|
||||
/// # Note
|
||||
///
|
||||
@ -92,11 +91,11 @@ pub trait ReflectCommandExt {
|
||||
///
|
||||
/// # Panics
|
||||
///
|
||||
/// - If the given [`Resource`] is not present in the [`World`].
|
||||
/// - If the given [`Resource`] is not present in the [`World`](crate::world::World).
|
||||
///
|
||||
/// # Note
|
||||
///
|
||||
/// - The given [`Resource`] is removed from the [`World`] before the command is applied.
|
||||
/// - The given [`Resource`] is removed from the [`World`](crate::world::World) before the command is applied.
|
||||
fn insert_reflect_with_registry<T: Resource + AsRef<TypeRegistry>>(
|
||||
&mut self,
|
||||
component: Box<dyn PartialReflect>,
|
||||
@ -214,7 +213,7 @@ impl<'w> EntityWorldMut<'w> {
|
||||
/// - If [`AppTypeRegistry`] does not have the reflection data for the given
|
||||
/// [`Component`](crate::component::Component) or [`Bundle`](crate::bundle::Bundle).
|
||||
/// - If the component or bundle data is invalid. See [`PartialReflect::apply`] for further details.
|
||||
/// - If [`AppTypeRegistry`] is not present in the [`World`].
|
||||
/// - If [`AppTypeRegistry`] is not present in the [`World`](crate::world::World).
|
||||
///
|
||||
/// # Note
|
||||
///
|
||||
@ -222,15 +221,10 @@ impl<'w> EntityWorldMut<'w> {
|
||||
/// is much slower.
|
||||
pub fn insert_reflect(&mut self, component: Box<dyn PartialReflect>) -> &mut Self {
|
||||
self.assert_not_despawned();
|
||||
let entity_id = self.id();
|
||||
self.world_scope(|world| {
|
||||
world.resource_scope(|world, registry: Mut<AppTypeRegistry>| {
|
||||
let type_registry = ®istry.as_ref().read();
|
||||
insert_reflect_with_registry_ref(world, entity_id, type_registry, component);
|
||||
});
|
||||
world.flush();
|
||||
self.resource_scope(|entity, registry: Mut<AppTypeRegistry>| {
|
||||
let type_registry = ®istry.as_ref().read();
|
||||
insert_reflect_with_registry_ref(entity, type_registry, component);
|
||||
});
|
||||
self.update_location();
|
||||
self
|
||||
}
|
||||
|
||||
@ -245,21 +239,16 @@ impl<'w> EntityWorldMut<'w> {
|
||||
/// - If the given [`Resource`] does not have the reflection data for the given
|
||||
/// [`Component`](crate::component::Component) or [`Bundle`](crate::bundle::Bundle).
|
||||
/// - If the component or bundle data is invalid. See [`PartialReflect::apply`] for further details.
|
||||
/// - If the given [`Resource`] is not present in the [`World`].
|
||||
/// - If the given [`Resource`] is not present in the [`World`](crate::world::World).
|
||||
pub fn insert_reflect_with_registry<T: Resource + AsRef<TypeRegistry>>(
|
||||
&mut self,
|
||||
component: Box<dyn PartialReflect>,
|
||||
) -> &mut Self {
|
||||
self.assert_not_despawned();
|
||||
let entity_id = self.id();
|
||||
self.world_scope(|world| {
|
||||
world.resource_scope(|world, registry: Mut<T>| {
|
||||
let type_registry = registry.as_ref().as_ref();
|
||||
insert_reflect_with_registry_ref(world, entity_id, type_registry, component);
|
||||
});
|
||||
world.flush();
|
||||
self.resource_scope(|entity, registry: Mut<T>| {
|
||||
let type_registry = registry.as_ref().as_ref();
|
||||
insert_reflect_with_registry_ref(entity, type_registry, component);
|
||||
});
|
||||
self.update_location();
|
||||
self
|
||||
}
|
||||
|
||||
@ -275,7 +264,7 @@ impl<'w> EntityWorldMut<'w> {
|
||||
/// # Panics
|
||||
///
|
||||
/// - If the entity has been despawned while this `EntityWorldMut` is still alive.
|
||||
/// - If [`AppTypeRegistry`] is not present in the [`World`].
|
||||
/// - If [`AppTypeRegistry`] is not present in the [`World`](crate::world::World).
|
||||
///
|
||||
/// # Note
|
||||
///
|
||||
@ -283,20 +272,10 @@ impl<'w> EntityWorldMut<'w> {
|
||||
/// is much slower.
|
||||
pub fn remove_reflect(&mut self, component_type_path: Cow<'static, str>) -> &mut Self {
|
||||
self.assert_not_despawned();
|
||||
let entity_id = self.id();
|
||||
self.world_scope(|world| {
|
||||
world.resource_scope(|world, registry: Mut<AppTypeRegistry>| {
|
||||
let type_registry = ®istry.as_ref().read();
|
||||
remove_reflect_with_registry_ref(
|
||||
world,
|
||||
entity_id,
|
||||
type_registry,
|
||||
component_type_path,
|
||||
);
|
||||
});
|
||||
world.flush();
|
||||
self.resource_scope(|entity, registry: Mut<AppTypeRegistry>| {
|
||||
let type_registry = ®istry.as_ref().read();
|
||||
remove_reflect_with_registry_ref(entity, type_registry, component_type_path);
|
||||
});
|
||||
self.update_location();
|
||||
self
|
||||
}
|
||||
|
||||
@ -313,34 +292,23 @@ impl<'w> EntityWorldMut<'w> {
|
||||
/// # Panics
|
||||
///
|
||||
/// - If the entity has been despawned while this `EntityWorldMut` is still alive.
|
||||
/// - If [`AppTypeRegistry`] is not present in the [`World`].
|
||||
/// - If [`AppTypeRegistry`] is not present in the [`World`](crate::world::World).
|
||||
pub fn remove_reflect_with_registry<T: Resource + AsRef<TypeRegistry>>(
|
||||
&mut self,
|
||||
component_type_path: Cow<'static, str>,
|
||||
) -> &mut Self {
|
||||
self.assert_not_despawned();
|
||||
let entity_id = self.id();
|
||||
self.world_scope(|world| {
|
||||
world.resource_scope(|world, registry: Mut<T>| {
|
||||
let type_registry = registry.as_ref().as_ref();
|
||||
remove_reflect_with_registry_ref(
|
||||
world,
|
||||
entity_id,
|
||||
type_registry,
|
||||
component_type_path,
|
||||
);
|
||||
});
|
||||
world.flush();
|
||||
self.resource_scope(|entity, registry: Mut<T>| {
|
||||
let type_registry = registry.as_ref().as_ref();
|
||||
remove_reflect_with_registry_ref(entity, type_registry, component_type_path);
|
||||
});
|
||||
self.update_location();
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
/// Helper function to add a reflect component or bundle to a given entity
|
||||
fn insert_reflect_with_registry_ref(
|
||||
world: &mut World,
|
||||
entity: Entity,
|
||||
entity: &mut EntityWorldMut,
|
||||
type_registry: &TypeRegistry,
|
||||
component: Box<dyn PartialReflect>,
|
||||
) {
|
||||
@ -348,18 +316,14 @@ fn insert_reflect_with_registry_ref(
|
||||
.get_represented_type_info()
|
||||
.expect("component should represent a type.");
|
||||
let type_path = type_info.type_path();
|
||||
let Ok(mut entity) = world.get_entity_mut(entity) else {
|
||||
panic!("error[B0003]: Could not insert a reflected component (of type {type_path}) for entity {entity}, which {}. See: https://bevy.org/learn/errors/b0003",
|
||||
world.entities().entity_does_not_exist_error_details(entity));
|
||||
};
|
||||
let Some(type_registration) = type_registry.get(type_info.type_id()) else {
|
||||
panic!("`{type_path}` should be registered in type registry via `App::register_type<{type_path}>`");
|
||||
};
|
||||
|
||||
if let Some(reflect_component) = type_registration.data::<ReflectComponent>() {
|
||||
reflect_component.insert(&mut entity, component.as_partial_reflect(), type_registry);
|
||||
reflect_component.insert(entity, component.as_partial_reflect(), type_registry);
|
||||
} else if let Some(reflect_bundle) = type_registration.data::<ReflectBundle>() {
|
||||
reflect_bundle.insert(&mut entity, component.as_partial_reflect(), type_registry);
|
||||
reflect_bundle.insert(entity, component.as_partial_reflect(), type_registry);
|
||||
} else {
|
||||
panic!("`{type_path}` should have #[reflect(Component)] or #[reflect(Bundle)]");
|
||||
}
|
||||
@ -367,21 +331,17 @@ fn insert_reflect_with_registry_ref(
|
||||
|
||||
/// Helper function to remove a reflect component or bundle from a given entity
|
||||
fn remove_reflect_with_registry_ref(
|
||||
world: &mut World,
|
||||
entity: Entity,
|
||||
entity: &mut EntityWorldMut,
|
||||
type_registry: &TypeRegistry,
|
||||
component_type_path: Cow<'static, str>,
|
||||
) {
|
||||
let Ok(mut entity) = world.get_entity_mut(entity) else {
|
||||
return;
|
||||
};
|
||||
let Some(type_registration) = type_registry.get_with_type_path(&component_type_path) else {
|
||||
return;
|
||||
};
|
||||
if let Some(reflect_component) = type_registration.data::<ReflectComponent>() {
|
||||
reflect_component.remove(&mut entity);
|
||||
reflect_component.remove(entity);
|
||||
} else if let Some(reflect_bundle) = type_registration.data::<ReflectBundle>() {
|
||||
reflect_bundle.remove(&mut entity);
|
||||
reflect_bundle.remove(entity);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -55,6 +55,8 @@ use variadics_please::{all_tuples, all_tuples_enumerated};
|
||||
///
|
||||
/// ```
|
||||
/// # use bevy_ecs::prelude::*;
|
||||
/// # #[derive(Component)]
|
||||
/// # struct SomeComponent;
|
||||
/// # #[derive(Resource)]
|
||||
/// # struct SomeResource;
|
||||
/// # #[derive(BufferedEvent)]
|
||||
@ -66,6 +68,8 @@ use variadics_please::{all_tuples, all_tuples_enumerated};
|
||||
/// # struct ParamsExample<'w, 's> {
|
||||
/// # query:
|
||||
/// Query<'w, 's, Entity>,
|
||||
/// # query2:
|
||||
/// Query<'w, 's, &'static SomeComponent>,
|
||||
/// # res:
|
||||
/// Res<'w, SomeResource>,
|
||||
/// # res_mut:
|
||||
|
@ -781,18 +781,18 @@ impl<'w> DeferredWorld<'w> {
|
||||
/// Triggers all event observers for [`ComponentId`] in target.
|
||||
///
|
||||
/// # Safety
|
||||
/// Caller must ensure observers listening for `event` can accept ZST pointers
|
||||
/// Caller must ensure observers listening for `event_key` can accept ZST pointers
|
||||
#[inline]
|
||||
pub(crate) unsafe fn trigger_observers(
|
||||
&mut self,
|
||||
event: EventKey,
|
||||
event_key: EventKey,
|
||||
target: Option<Entity>,
|
||||
components: impl Iterator<Item = ComponentId> + Clone,
|
||||
caller: MaybeLocation,
|
||||
) {
|
||||
Observers::invoke::<_>(
|
||||
self.reborrow(),
|
||||
event,
|
||||
event_key,
|
||||
target,
|
||||
target,
|
||||
components,
|
||||
@ -805,11 +805,11 @@ impl<'w> DeferredWorld<'w> {
|
||||
/// Triggers all event observers for [`ComponentId`] in target.
|
||||
///
|
||||
/// # Safety
|
||||
/// Caller must ensure `E` is accessible as the type represented by `event`
|
||||
/// Caller must ensure `E` is accessible as the type represented by `event_key`
|
||||
#[inline]
|
||||
pub(crate) unsafe fn trigger_observers_with_data<E, T>(
|
||||
&mut self,
|
||||
event: EventKey,
|
||||
event_key: EventKey,
|
||||
current_target: Option<Entity>,
|
||||
original_target: Option<Entity>,
|
||||
components: impl Iterator<Item = ComponentId> + Clone,
|
||||
@ -821,7 +821,7 @@ impl<'w> DeferredWorld<'w> {
|
||||
{
|
||||
Observers::invoke::<_>(
|
||||
self.reborrow(),
|
||||
event,
|
||||
event_key,
|
||||
current_target,
|
||||
original_target,
|
||||
components.clone(),
|
||||
@ -849,7 +849,7 @@ impl<'w> DeferredWorld<'w> {
|
||||
}
|
||||
Observers::invoke::<_>(
|
||||
self.reborrow(),
|
||||
event,
|
||||
event_key,
|
||||
Some(current_target),
|
||||
original_target,
|
||||
components.clone(),
|
||||
|
@ -1547,6 +1547,50 @@ impl<'w> EntityWorldMut<'w> {
|
||||
self.world.get_resource_mut()
|
||||
}
|
||||
|
||||
/// Temporarily removes the requested resource from the [`World`], runs custom user code,
|
||||
/// then re-adds the resource before returning.
|
||||
///
|
||||
/// # Panics
|
||||
///
|
||||
/// Panics if the resource does not exist.
|
||||
/// Use [`try_resource_scope`](Self::try_resource_scope) instead if you want to handle this case.
|
||||
///
|
||||
/// See [`World::resource_scope`] for further details.
|
||||
#[track_caller]
|
||||
pub fn resource_scope<R: Resource, U>(
|
||||
&mut self,
|
||||
f: impl FnOnce(&mut EntityWorldMut, Mut<R>) -> U,
|
||||
) -> U {
|
||||
let id = self.id();
|
||||
self.world_scope(|world| {
|
||||
world.resource_scope(|world, res| {
|
||||
// Acquiring a new EntityWorldMut here and using that instead of `self` is fine because
|
||||
// the outer `world_scope` will handle updating our location if it gets changed by the user code
|
||||
let mut this = world.entity_mut(id);
|
||||
f(&mut this, res)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
/// Temporarily removes the requested resource from the [`World`] if it exists, runs custom user code,
|
||||
/// then re-adds the resource before returning. Returns `None` if the resource does not exist in the [`World`].
|
||||
///
|
||||
/// See [`World::try_resource_scope`] for further details.
|
||||
pub fn try_resource_scope<R: Resource, U>(
|
||||
&mut self,
|
||||
f: impl FnOnce(&mut EntityWorldMut, Mut<R>) -> U,
|
||||
) -> Option<U> {
|
||||
let id = self.id();
|
||||
self.world_scope(|world| {
|
||||
world.try_resource_scope(|world, res| {
|
||||
// Acquiring a new EntityWorldMut here and using that instead of `self` is fine because
|
||||
// the outer `world_scope` will handle updating our location if it gets changed by the user code
|
||||
let mut this = world.entity_mut(id);
|
||||
f(&mut this, res)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
/// Retrieves the change ticks for the given component. This can be useful for implementing change
|
||||
/// detection in custom runtimes.
|
||||
///
|
||||
@ -4959,6 +5003,46 @@ mod tests {
|
||||
assert!(entity.get_mut_by_id(invalid_component_id).is_err());
|
||||
}
|
||||
|
||||
#[derive(Resource)]
|
||||
struct R(usize);
|
||||
|
||||
#[test]
|
||||
fn entity_mut_resource_scope() {
|
||||
// Keep in sync with the `resource_scope` test in lib.rs
|
||||
let mut world = World::new();
|
||||
let mut entity = world.spawn_empty();
|
||||
|
||||
assert!(entity.try_resource_scope::<R, _>(|_, _| {}).is_none());
|
||||
entity.world_scope(|world| world.insert_resource(R(0)));
|
||||
entity.resource_scope(|entity: &mut EntityWorldMut, mut value: Mut<R>| {
|
||||
value.0 += 1;
|
||||
assert!(!entity.world().contains_resource::<R>());
|
||||
});
|
||||
assert_eq!(entity.resource::<R>().0, 1);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn entity_mut_resource_scope_panic() {
|
||||
let mut world = World::new();
|
||||
world.insert_resource(R(0));
|
||||
|
||||
let mut entity = world.spawn_empty();
|
||||
let old_location = entity.location();
|
||||
let result = std::panic::catch_unwind(AssertUnwindSafe(|| {
|
||||
entity.resource_scope(|entity: &mut EntityWorldMut, _: Mut<R>| {
|
||||
// Change the entity's `EntityLocation`.
|
||||
entity.insert(TestComponent(0));
|
||||
|
||||
// Ensure that the entity location still gets updated even in case of a panic.
|
||||
panic!("this should get caught by the outer scope")
|
||||
});
|
||||
}));
|
||||
assert!(result.is_err());
|
||||
|
||||
// Ensure that the location has been properly updated.
|
||||
assert_ne!(entity.location(), old_location);
|
||||
}
|
||||
|
||||
// regression test for https://github.com/bevyengine/bevy/pull/7387
|
||||
#[test]
|
||||
fn entity_mut_world_scope_panic() {
|
||||
@ -4983,6 +5067,28 @@ mod tests {
|
||||
assert_ne!(entity.location(), old_location);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn entity_mut_reborrow_scope_panic() {
|
||||
let mut world = World::new();
|
||||
|
||||
let mut entity = world.spawn_empty();
|
||||
let old_location = entity.location();
|
||||
let res = std::panic::catch_unwind(AssertUnwindSafe(|| {
|
||||
entity.reborrow_scope(|mut entity| {
|
||||
// Change the entity's `EntityLocation`, which invalidates the original `EntityWorldMut`.
|
||||
// This will get updated at the end of the scope.
|
||||
entity.insert(TestComponent(0));
|
||||
|
||||
// Ensure that the entity location still gets updated even in case of a panic.
|
||||
panic!("this should get caught by the outer scope")
|
||||
});
|
||||
}));
|
||||
assert!(res.is_err());
|
||||
|
||||
// Ensure that the location has been properly updated.
|
||||
assert_ne!(entity.location(), old_location);
|
||||
}
|
||||
|
||||
// regression test for https://github.com/bevyengine/bevy/pull/7805
|
||||
#[test]
|
||||
fn removing_sparse_updates_archetype_row() {
|
||||
@ -5420,9 +5526,6 @@ mod tests {
|
||||
#[derive(Component)]
|
||||
struct A;
|
||||
|
||||
#[derive(Resource)]
|
||||
struct R;
|
||||
|
||||
#[test]
|
||||
fn disjoint_access() {
|
||||
fn disjoint_readonly(_: Query<EntityMut, With<A>>, _: Query<EntityRef, Without<A>>) {}
|
||||
|
@ -2551,6 +2551,11 @@ impl World {
|
||||
/// This enables safe simultaneous mutable access to both a resource and the rest of the [`World`].
|
||||
/// For more complex access patterns, consider using [`SystemState`](crate::system::SystemState).
|
||||
///
|
||||
/// # Panics
|
||||
///
|
||||
/// Panics if the resource does not exist.
|
||||
/// Use [`try_resource_scope`](Self::try_resource_scope) instead if you want to handle this case.
|
||||
///
|
||||
/// # Example
|
||||
/// ```
|
||||
/// use bevy_ecs::prelude::*;
|
||||
@ -2568,8 +2573,6 @@ impl World {
|
||||
/// });
|
||||
/// assert_eq!(world.get_resource::<A>().unwrap().0, 2);
|
||||
/// ```
|
||||
///
|
||||
/// See also [`try_resource_scope`](Self::try_resource_scope).
|
||||
#[track_caller]
|
||||
pub fn resource_scope<R: Resource, U>(&mut self, f: impl FnOnce(&mut World, Mut<R>) -> U) -> U {
|
||||
self.try_resource_scope(f)
|
||||
|
@ -826,11 +826,26 @@ impl Mesh {
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// Returns [`Err(MergeMeshError)`](MergeMeshError) if the vertex attribute values of `other` are incompatible with `self`.
|
||||
/// For example, [`VertexAttributeValues::Float32`] is incompatible with [`VertexAttributeValues::Float32x3`].
|
||||
pub fn merge(&mut self, other: &Mesh) -> Result<(), MergeMeshError> {
|
||||
/// If any of the following conditions are not met, this function errors:
|
||||
/// * All of the vertex attributes that have the same attribute id, must also
|
||||
/// have the same attribute type.
|
||||
/// For example two attributes with the same id, but where one is a
|
||||
/// [`VertexAttributeValues::Float32`] and the other is a
|
||||
/// [`VertexAttributeValues::Float32x3`], would be invalid.
|
||||
/// * Both meshes must have the same primitive topology.
|
||||
pub fn merge(&mut self, other: &Mesh) -> Result<(), MeshMergeError> {
|
||||
use VertexAttributeValues::*;
|
||||
|
||||
// Check if the meshes `primitive_topology` field is the same,
|
||||
// as if that is not the case, the resulting mesh could (and most likely would)
|
||||
// be invalid.
|
||||
if self.primitive_topology != other.primitive_topology {
|
||||
return Err(MeshMergeError::IncompatiblePrimitiveTopology {
|
||||
self_primitive_topology: self.primitive_topology,
|
||||
other_primitive_topology: other.primitive_topology,
|
||||
});
|
||||
}
|
||||
|
||||
// The indices of `other` should start after the last vertex of `self`.
|
||||
let index_offset = self.count_vertices();
|
||||
|
||||
@ -871,7 +886,7 @@ impl Mesh {
|
||||
(Uint8x4(vec1), Uint8x4(vec2)) => vec1.extend(vec2),
|
||||
(Unorm8x4(vec1), Unorm8x4(vec2)) => vec1.extend(vec2),
|
||||
_ => {
|
||||
return Err(MergeMeshError {
|
||||
return Err(MeshMergeError::IncompatibleVertexAttributes {
|
||||
self_attribute: *attribute,
|
||||
other_attribute: other
|
||||
.attribute_data(attribute.id)
|
||||
@ -1391,10 +1406,21 @@ impl MeshDeserializer {
|
||||
|
||||
/// Error that can occur when calling [`Mesh::merge`].
|
||||
#[derive(Error, Debug, Clone)]
|
||||
#[error("Incompatible vertex attribute types {} and {}", self_attribute.name, other_attribute.map(|a| a.name).unwrap_or("None"))]
|
||||
pub struct MergeMeshError {
|
||||
pub self_attribute: MeshVertexAttribute,
|
||||
pub other_attribute: Option<MeshVertexAttribute>,
|
||||
pub enum MeshMergeError {
|
||||
#[error("Incompatible vertex attribute types: {} and {}", self_attribute.name, other_attribute.map(|a| a.name).unwrap_or("None"))]
|
||||
IncompatibleVertexAttributes {
|
||||
self_attribute: MeshVertexAttribute,
|
||||
other_attribute: Option<MeshVertexAttribute>,
|
||||
},
|
||||
#[error(
|
||||
"Incompatible primitive topologies: {:?} and {:?}",
|
||||
self_primitive_topology,
|
||||
other_primitive_topology
|
||||
)]
|
||||
IncompatiblePrimitiveTopology {
|
||||
self_primitive_topology: PrimitiveTopology,
|
||||
other_primitive_topology: PrimitiveTopology,
|
||||
},
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
|
@ -220,6 +220,21 @@ impl Timer {
|
||||
self.duration = duration;
|
||||
}
|
||||
|
||||
/// Finishes the timer.
|
||||
///
|
||||
/// # Examples
|
||||
/// ```
|
||||
/// # use bevy_time::*;
|
||||
/// let mut timer = Timer::from_seconds(1.5, TimerMode::Once);
|
||||
/// timer.finish();
|
||||
/// assert!(timer.finished());
|
||||
/// ```
|
||||
#[inline]
|
||||
pub fn finish(&mut self) {
|
||||
let remaining = self.remaining();
|
||||
self.tick(remaining);
|
||||
}
|
||||
|
||||
/// Returns the mode of the timer.
|
||||
///
|
||||
/// # Examples
|
||||
|
@ -44,37 +44,38 @@ impl BuildChildrenTransformExt for EntityCommands<'_> {
|
||||
|
||||
impl BuildChildrenTransformExt for EntityWorldMut<'_> {
|
||||
fn set_parent_in_place(&mut self, parent: Entity) -> &mut Self {
|
||||
let child = self.id();
|
||||
self.world_scope(|world| {
|
||||
world.entity_mut(parent).add_child(child);
|
||||
// FIXME: Replace this closure with a `try` block. See: https://github.com/rust-lang/rust/issues/31436.
|
||||
let mut update_transform = || {
|
||||
let parent = *world.get_entity(parent).ok()?.get::<GlobalTransform>()?;
|
||||
let child_global = *world.get_entity(child).ok()?.get::<GlobalTransform>()?;
|
||||
let mut child_entity = world.get_entity_mut(child).ok()?;
|
||||
let mut child = child_entity.get_mut::<Transform>()?;
|
||||
*child = child_global.reparented_to(&parent);
|
||||
Some(())
|
||||
};
|
||||
update_transform();
|
||||
});
|
||||
// FIXME: Replace this closure with a `try` block. See: https://github.com/rust-lang/rust/issues/31436.
|
||||
let mut update_transform = || {
|
||||
let child = self.id();
|
||||
let parent_global = self.world_scope(|world| {
|
||||
world
|
||||
.get_entity_mut(parent)
|
||||
.ok()?
|
||||
.add_child(child)
|
||||
.get::<GlobalTransform>()
|
||||
.copied()
|
||||
})?;
|
||||
let child_global = self.get::<GlobalTransform>()?;
|
||||
let new_child_local = child_global.reparented_to(&parent_global);
|
||||
let mut child_local = self.get_mut::<Transform>()?;
|
||||
*child_local = new_child_local;
|
||||
Some(())
|
||||
};
|
||||
update_transform();
|
||||
self
|
||||
}
|
||||
|
||||
fn remove_parent_in_place(&mut self) -> &mut Self {
|
||||
let child = self.id();
|
||||
self.world_scope(|world| {
|
||||
world.entity_mut(child).remove::<ChildOf>();
|
||||
// FIXME: Replace this closure with a `try` block. See: https://github.com/rust-lang/rust/issues/31436.
|
||||
let mut update_transform = || {
|
||||
let child_global = *world.get_entity(child).ok()?.get::<GlobalTransform>()?;
|
||||
let mut child_entity = world.get_entity_mut(child).ok()?;
|
||||
let mut child = child_entity.get_mut::<Transform>()?;
|
||||
*child = child_global.compute_transform();
|
||||
Some(())
|
||||
};
|
||||
update_transform();
|
||||
});
|
||||
self.remove::<ChildOf>();
|
||||
// FIXME: Replace this closure with a `try` block. See: https://github.com/rust-lang/rust/issues/31436.
|
||||
let mut update_transform = || {
|
||||
let global = self.get::<GlobalTransform>()?;
|
||||
let new_local = global.compute_transform();
|
||||
let mut local = self.get_mut::<Transform>()?;
|
||||
*local = new_local;
|
||||
Some(())
|
||||
};
|
||||
update_transform();
|
||||
self
|
||||
}
|
||||
}
|
||||
|
@ -7,7 +7,7 @@ use core::{
|
||||
use super::shader_flags::BORDER_ALL;
|
||||
use crate::*;
|
||||
use bevy_asset::*;
|
||||
use bevy_color::{ColorToComponents, LinearRgba};
|
||||
use bevy_color::{ColorToComponents, Hsla, Hsva, LinearRgba, Oklaba, Oklcha, Srgba};
|
||||
use bevy_ecs::{
|
||||
prelude::Component,
|
||||
system::{
|
||||
@ -654,6 +654,44 @@ struct UiGradientVertex {
|
||||
hint: f32,
|
||||
}
|
||||
|
||||
fn convert_color_to_space(color: LinearRgba, space: InterpolationColorSpace) -> [f32; 4] {
|
||||
match space {
|
||||
InterpolationColorSpace::Oklaba => {
|
||||
let oklaba: Oklaba = color.into();
|
||||
[oklaba.lightness, oklaba.a, oklaba.b, oklaba.alpha]
|
||||
}
|
||||
InterpolationColorSpace::Oklcha | InterpolationColorSpace::OklchaLong => {
|
||||
let oklcha: Oklcha = color.into();
|
||||
[
|
||||
oklcha.lightness,
|
||||
oklcha.chroma,
|
||||
oklcha.hue.to_radians(),
|
||||
oklcha.alpha,
|
||||
]
|
||||
}
|
||||
InterpolationColorSpace::Srgba => {
|
||||
let srgba: Srgba = color.into();
|
||||
[srgba.red, srgba.green, srgba.blue, srgba.alpha]
|
||||
}
|
||||
InterpolationColorSpace::LinearRgba => color.to_f32_array(),
|
||||
InterpolationColorSpace::Hsla | InterpolationColorSpace::HslaLong => {
|
||||
let hsla: Hsla = color.into();
|
||||
// Normalize hue to 0..1 range for shader
|
||||
[
|
||||
hsla.hue / 360.0,
|
||||
hsla.saturation,
|
||||
hsla.lightness,
|
||||
hsla.alpha,
|
||||
]
|
||||
}
|
||||
InterpolationColorSpace::Hsva | InterpolationColorSpace::HsvaLong => {
|
||||
let hsva: Hsva = color.into();
|
||||
// Normalize hue to 0..1 range for shader
|
||||
[hsva.hue / 360.0, hsva.saturation, hsva.value, hsva.alpha]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn prepare_gradient(
|
||||
mut commands: Commands,
|
||||
render_device: Res<RenderDevice>,
|
||||
@ -804,8 +842,9 @@ pub fn prepare_gradient(
|
||||
continue;
|
||||
}
|
||||
}
|
||||
let start_color = start_stop.0.to_f32_array();
|
||||
let end_color = end_stop.0.to_f32_array();
|
||||
let start_color =
|
||||
convert_color_to_space(start_stop.0, gradient.color_space);
|
||||
let end_color = convert_color_to_space(end_stop.0, gradient.color_space);
|
||||
let mut stop_flags = flags;
|
||||
if 0. < start_stop.1
|
||||
&& (stop_index == gradient.stops_range.start || segment_count == 0)
|
||||
|
@ -114,26 +114,6 @@ fn fragment(in: GradientVertexOutput) -> @location(0) vec4<f32> {
|
||||
}
|
||||
}
|
||||
|
||||
// This function converts two linear rgba colors to srgba space, mixes them, and then converts the result back to linear rgb space.
|
||||
fn mix_linear_rgba_in_srgba_space(a: vec4<f32>, b: vec4<f32>, t: f32) -> vec4<f32> {
|
||||
let a_srgb = pow(a.rgb, vec3(1. / 2.2));
|
||||
let b_srgb = pow(b.rgb, vec3(1. / 2.2));
|
||||
let mixed_srgb = mix(a_srgb, b_srgb, t);
|
||||
return vec4(pow(mixed_srgb, vec3(2.2)), mix(a.a, b.a, t));
|
||||
}
|
||||
|
||||
fn linear_rgba_to_oklaba(c: vec4<f32>) -> vec4<f32> {
|
||||
let l = pow(0.41222146 * c.x + 0.53633255 * c.y + 0.051445995 * c.z, 1. / 3.);
|
||||
let m = pow(0.2119035 * c.x + 0.6806995 * c.y + 0.10739696 * c.z, 1. / 3.);
|
||||
let s = pow(0.08830246 * c.x + 0.28171885 * c.y + 0.6299787 * c.z, 1. / 3.);
|
||||
return vec4(
|
||||
0.21045426 * l + 0.7936178 * m - 0.004072047 * s,
|
||||
1.9779985 * l - 2.4285922 * m + 0.4505937 * s,
|
||||
0.025904037 * l + 0.78277177 * m - 0.80867577 * s,
|
||||
c.a
|
||||
);
|
||||
}
|
||||
|
||||
fn oklaba_to_linear_rgba(c: vec4<f32>) -> vec4<f32> {
|
||||
let l_ = c.x + 0.39633778 * c.y + 0.21580376 * c.z;
|
||||
let m_ = c.x - 0.105561346 * c.y - 0.06385417 * c.z;
|
||||
@ -149,33 +129,6 @@ fn oklaba_to_linear_rgba(c: vec4<f32>) -> vec4<f32> {
|
||||
);
|
||||
}
|
||||
|
||||
fn mix_linear_rgba_in_oklaba_space(a: vec4<f32>, b: vec4<f32>, t: f32) -> vec4<f32> {
|
||||
return oklaba_to_linear_rgba(mix(linear_rgba_to_oklaba(a), linear_rgba_to_oklaba(b), t));
|
||||
}
|
||||
|
||||
fn linear_rgba_to_hsla(c: vec4<f32>) -> vec4<f32> {
|
||||
let max = max(max(c.r, c.g), c.b);
|
||||
let min = min(min(c.r, c.g), c.b);
|
||||
let l = (max + min) * 0.5;
|
||||
if max == min {
|
||||
return vec4(0., 0., l, c.a);
|
||||
} else {
|
||||
let delta = max - min;
|
||||
let s = delta / (1. - abs(2. * l - 1.));
|
||||
var h = 0.;
|
||||
if max == c.r {
|
||||
h = ((c.g - c.b) / delta) % 6.;
|
||||
} else if max == c.g {
|
||||
h = ((c.b - c.r) / delta) + 2.;
|
||||
} else {
|
||||
h = ((c.r - c.g) / delta) + 4.;
|
||||
}
|
||||
h = h / 6.;
|
||||
return vec4<f32>(h, s, l, c.a);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
fn hsla_to_linear_rgba(hsl: vec4<f32>) -> vec4<f32> {
|
||||
let h = hsl.x;
|
||||
let s = hsl.y;
|
||||
@ -203,30 +156,6 @@ fn hsla_to_linear_rgba(hsl: vec4<f32>) -> vec4<f32> {
|
||||
return vec4<f32>(r + m, g + m, b + m, hsl.a);
|
||||
}
|
||||
|
||||
fn linear_rgba_to_hsva(c: vec4<f32>) -> vec4<f32> {
|
||||
let maxc = max(max(c.r, c.g), c.b);
|
||||
let minc = min(min(c.r, c.g), c.b);
|
||||
let delta = maxc - minc;
|
||||
var h: f32 = 0.0;
|
||||
var s: f32 = 0.0;
|
||||
let v: f32 = maxc;
|
||||
if delta != 0.0 {
|
||||
s = delta / maxc;
|
||||
if maxc == c.r {
|
||||
h = ((c.g - c.b) / delta) % 6.0;
|
||||
} else if maxc == c.g {
|
||||
h = ((c.b - c.r) / delta) + 2.0;
|
||||
} else {
|
||||
h = ((c.r - c.g) / delta) + 4.0;
|
||||
}
|
||||
h = h / 6.0;
|
||||
if h < 0.0 {
|
||||
h = h + 1.0;
|
||||
}
|
||||
}
|
||||
return vec4<f32>(h, s, v, c.a);
|
||||
}
|
||||
|
||||
fn hsva_to_linear_rgba(hsva: vec4<f32>) -> vec4<f32> {
|
||||
let h = hsva.x * 6.0;
|
||||
let s = hsva.y;
|
||||
@ -253,14 +182,6 @@ fn hsva_to_linear_rgba(hsva: vec4<f32>) -> vec4<f32> {
|
||||
return vec4<f32>(r + m, g + m, b + m, hsva.a);
|
||||
}
|
||||
|
||||
/// hue is left in radians and not converted to degrees
|
||||
fn linear_rgba_to_oklcha(c: vec4<f32>) -> vec4<f32> {
|
||||
let o = linear_rgba_to_oklaba(c);
|
||||
let chroma = sqrt(o.y * o.y + o.z * o.z);
|
||||
let hue = atan2(o.z, o.y);
|
||||
return vec4(o.x, chroma, rem_euclid(hue, TAU), o.a);
|
||||
}
|
||||
|
||||
fn oklcha_to_linear_rgba(c: vec4<f32>) -> vec4<f32> {
|
||||
let a = c.y * cos(c.z);
|
||||
let b = c.y * sin(c.z);
|
||||
@ -271,90 +192,6 @@ fn rem_euclid(a: f32, b: f32) -> f32 {
|
||||
return ((a % b) + b) % b;
|
||||
}
|
||||
|
||||
fn lerp_hue(a: f32, b: f32, t: f32) -> f32 {
|
||||
let diff = rem_euclid(b - a + PI, TAU) - PI;
|
||||
return rem_euclid(a + diff * t, TAU);
|
||||
}
|
||||
|
||||
fn lerp_hue_long(a: f32, b: f32, t: f32) -> f32 {
|
||||
let diff = rem_euclid(b - a + PI, TAU) - PI;
|
||||
return rem_euclid(a + (diff + select(TAU, -TAU, 0. < diff)) * t, TAU);
|
||||
}
|
||||
|
||||
fn mix_oklcha(a: vec4<f32>, b: vec4<f32>, t: f32) -> vec4<f32> {
|
||||
let ah = select(a.z, b.z, a.y == 0.);
|
||||
let bh = select(b.z, a.z, b.y == 0.);
|
||||
return vec4(
|
||||
mix(a.xy, b.xy, t),
|
||||
lerp_hue(ah, bh, t),
|
||||
mix(a.a, b.a, t)
|
||||
);
|
||||
}
|
||||
|
||||
fn mix_oklcha_long(a: vec4<f32>, b: vec4<f32>, t: f32) -> vec4<f32> {
|
||||
let ah = select(a.z, b.z, a.y == 0.);
|
||||
let bh = select(b.z, a.z, b.y == 0.);
|
||||
return vec4(
|
||||
mix(a.xy, b.xy, t),
|
||||
lerp_hue_long(ah, bh, t),
|
||||
mix(a.w, b.w, t)
|
||||
);
|
||||
}
|
||||
|
||||
fn mix_linear_rgba_in_oklcha_space(a: vec4<f32>, b: vec4<f32>, t: f32) -> vec4<f32> {
|
||||
return oklcha_to_linear_rgba(mix_oklcha(linear_rgba_to_oklcha(a), linear_rgba_to_oklcha(b), t));
|
||||
}
|
||||
|
||||
fn mix_linear_rgba_in_oklcha_space_long(a: vec4<f32>, b: vec4<f32>, t: f32) -> vec4<f32> {
|
||||
return oklcha_to_linear_rgba(mix_oklcha_long(linear_rgba_to_oklcha(a), linear_rgba_to_oklcha(b), t));
|
||||
}
|
||||
|
||||
fn mix_linear_rgba_in_hsva_space(a: vec4<f32>, b: vec4<f32>, t: f32) -> vec4<f32> {
|
||||
let ha = linear_rgba_to_hsva(a);
|
||||
let hb = linear_rgba_to_hsva(b);
|
||||
var h: f32;
|
||||
if ha.y == 0. {
|
||||
h = hb.x;
|
||||
} else if hb.y == 0. {
|
||||
h = ha.x;
|
||||
} else {
|
||||
h = lerp_hue(ha.x * TAU, hb.x * TAU, t) / TAU;
|
||||
}
|
||||
let s = mix(ha.y, hb.y, t);
|
||||
let v = mix(ha.z, hb.z, t);
|
||||
let a_alpha = mix(ha.a, hb.a, t);
|
||||
return hsva_to_linear_rgba(vec4<f32>(h, s, v, a_alpha));
|
||||
}
|
||||
|
||||
fn mix_linear_rgba_in_hsva_space_long(a: vec4<f32>, b: vec4<f32>, t: f32) -> vec4<f32> {
|
||||
let ha = linear_rgba_to_hsva(a);
|
||||
let hb = linear_rgba_to_hsva(b);
|
||||
let h = lerp_hue_long(ha.x * TAU, hb.x * TAU, t) / TAU;
|
||||
let s = mix(ha.y, hb.y, t);
|
||||
let v = mix(ha.z, hb.z, t);
|
||||
let a_alpha = mix(ha.a, hb.a, t);
|
||||
return hsva_to_linear_rgba(vec4<f32>(h, s, v, a_alpha));
|
||||
}
|
||||
|
||||
fn mix_linear_rgba_in_hsla_space(a: vec4<f32>, b: vec4<f32>, t: f32) -> vec4<f32> {
|
||||
let ha = linear_rgba_to_hsla(a);
|
||||
let hb = linear_rgba_to_hsla(b);
|
||||
let h = lerp_hue(ha.x * TAU, hb.x * TAU, t) / TAU;
|
||||
let s = mix(ha.y, hb.y, t);
|
||||
let l = mix(ha.z, hb.z, t);
|
||||
let a_alpha = mix(ha.a, hb.a, t);
|
||||
return hsla_to_linear_rgba(vec4<f32>(h, s, l, a_alpha));
|
||||
}
|
||||
|
||||
fn mix_linear_rgba_in_hsla_space_long(a: vec4<f32>, b: vec4<f32>, t: f32) -> vec4<f32> {
|
||||
let ha = linear_rgba_to_hsla(a);
|
||||
let hb = linear_rgba_to_hsla(b);
|
||||
let h = lerp_hue_long(ha.x * TAU, hb.x * TAU, t) / TAU;
|
||||
let s = mix(ha.y, hb.y, t);
|
||||
let l = mix(ha.z, hb.z, t);
|
||||
let a_alpha = mix(ha.a, hb.a, t);
|
||||
return hsla_to_linear_rgba(vec4<f32>(h, s, l, a_alpha));
|
||||
}
|
||||
|
||||
// These functions are used to calculate the distance in gradient space from the start of the gradient to the point.
|
||||
// The distance in gradient space is then used to interpolate between the start and end colors.
|
||||
@ -386,6 +223,105 @@ fn conic_distance(
|
||||
return (((angle - start) % TAU) + TAU) % TAU;
|
||||
}
|
||||
|
||||
fn mix_oklcha(a: vec4<f32>, b: vec4<f32>, t: f32) -> vec4<f32> {
|
||||
let hue_diff = b.z - a.z;
|
||||
var adjusted_hue = a.z;
|
||||
if abs(hue_diff) > PI {
|
||||
if hue_diff > 0.0 {
|
||||
adjusted_hue = a.z + (hue_diff - TAU) * t;
|
||||
} else {
|
||||
adjusted_hue = a.z + (hue_diff + TAU) * t;
|
||||
}
|
||||
} else {
|
||||
adjusted_hue = a.z + hue_diff * t;
|
||||
}
|
||||
return vec4(
|
||||
mix(a.x, b.x, t),
|
||||
mix(a.y, b.y, t),
|
||||
rem_euclid(adjusted_hue, TAU),
|
||||
mix(a.w, b.w, t)
|
||||
);
|
||||
}
|
||||
|
||||
fn mix_oklcha_long(a: vec4<f32>, b: vec4<f32>, t: f32) -> vec4<f32> {
|
||||
let hue_diff = b.z - a.z;
|
||||
var adjusted_hue = a.z;
|
||||
if abs(hue_diff) < PI {
|
||||
if hue_diff >= 0.0 {
|
||||
adjusted_hue = a.z + (hue_diff - TAU) * t;
|
||||
} else {
|
||||
adjusted_hue = a.z + (hue_diff + TAU) * t;
|
||||
}
|
||||
} else {
|
||||
adjusted_hue = a.z + hue_diff * t;
|
||||
}
|
||||
return vec4(
|
||||
mix(a.x, b.x, t),
|
||||
mix(a.y, b.y, t),
|
||||
rem_euclid(adjusted_hue, TAU),
|
||||
mix(a.w, b.w, t)
|
||||
);
|
||||
}
|
||||
|
||||
fn mix_hsla(a: vec4<f32>, b: vec4<f32>, t: f32) -> vec4<f32> {
|
||||
return vec4(
|
||||
fract(a.x + (fract(b.x - a.x + 0.5) - 0.5) * t),
|
||||
mix(a.y, b.y, t),
|
||||
mix(a.z, b.z, t),
|
||||
mix(a.w, b.w, t)
|
||||
);
|
||||
}
|
||||
|
||||
fn mix_hsla_long(a: vec4<f32>, b: vec4<f32>, t: f32) -> vec4<f32> {
|
||||
let d = fract(b.x - a.x + 0.5) - 0.5;
|
||||
return vec4(
|
||||
fract(a.x + (d + select(1., -1., 0. < d)) * t),
|
||||
mix(a.y, b.y, t),
|
||||
mix(a.z, b.z, t),
|
||||
mix(a.w, b.w, t)
|
||||
);
|
||||
}
|
||||
|
||||
fn mix_hsva(a: vec4<f32>, b: vec4<f32>, t: f32) -> vec4<f32> {
|
||||
let hue_diff = b.x - a.x;
|
||||
var adjusted_hue = a.x;
|
||||
if abs(hue_diff) > 0.5 {
|
||||
if hue_diff > 0.0 {
|
||||
adjusted_hue = a.x + (hue_diff - 1.0) * t;
|
||||
} else {
|
||||
adjusted_hue = a.x + (hue_diff + 1.0) * t;
|
||||
}
|
||||
} else {
|
||||
adjusted_hue = a.x + hue_diff * t;
|
||||
}
|
||||
return vec4(
|
||||
fract(adjusted_hue),
|
||||
mix(a.y, b.y, t),
|
||||
mix(a.z, b.z, t),
|
||||
mix(a.w, b.w, t)
|
||||
);
|
||||
}
|
||||
|
||||
fn mix_hsva_long(a: vec4<f32>, b: vec4<f32>, t: f32) -> vec4<f32> {
|
||||
let hue_diff = b.x - a.x;
|
||||
var adjusted_hue = a.x;
|
||||
if abs(hue_diff) < 0.5 {
|
||||
if hue_diff >= 0.0 {
|
||||
adjusted_hue = a.x + (hue_diff - 1.0) * t;
|
||||
} else {
|
||||
adjusted_hue = a.x + (hue_diff + 1.0) * t;
|
||||
}
|
||||
} else {
|
||||
adjusted_hue = a.x + hue_diff * t;
|
||||
}
|
||||
return vec4(
|
||||
fract(adjusted_hue),
|
||||
mix(a.y, b.y, t),
|
||||
mix(a.z, b.z, t),
|
||||
mix(a.w, b.w, t)
|
||||
);
|
||||
}
|
||||
|
||||
fn interpolate_gradient(
|
||||
distance: f32,
|
||||
start_color: vec4<f32>,
|
||||
@ -397,10 +333,10 @@ fn interpolate_gradient(
|
||||
) -> vec4<f32> {
|
||||
if start_distance == end_distance {
|
||||
if distance <= start_distance && enabled(flags, FILL_START) {
|
||||
return start_color;
|
||||
return convert_to_linear_rgba(start_color);
|
||||
}
|
||||
if start_distance <= distance && enabled(flags, FILL_END) {
|
||||
return end_color;
|
||||
return convert_to_linear_rgba(end_color);
|
||||
}
|
||||
return vec4(0.);
|
||||
}
|
||||
@ -409,14 +345,14 @@ fn interpolate_gradient(
|
||||
|
||||
if t < 0.0 {
|
||||
if enabled(flags, FILL_START) {
|
||||
return start_color;
|
||||
return convert_to_linear_rgba(start_color);
|
||||
}
|
||||
return vec4(0.0);
|
||||
}
|
||||
|
||||
if 1. < t {
|
||||
if enabled(flags, FILL_END) {
|
||||
return end_color;
|
||||
return convert_to_linear_rgba(end_color);
|
||||
}
|
||||
return vec4(0.0);
|
||||
}
|
||||
@ -426,24 +362,56 @@ fn interpolate_gradient(
|
||||
} else {
|
||||
t = 0.5 * (1 + (t - hint) / (1.0 - hint));
|
||||
}
|
||||
|
||||
#ifdef IN_SRGB
|
||||
return mix_linear_rgba_in_srgba_space(start_color, end_color, t);
|
||||
#else ifdef IN_OKLAB
|
||||
return mix_linear_rgba_in_oklaba_space(start_color, end_color, t);
|
||||
#else ifdef IN_OKLCH
|
||||
return mix_linear_rgba_in_oklcha_space(start_color, end_color, t);
|
||||
|
||||
return convert_to_linear_rgba(mix_colors(start_color, end_color, t));
|
||||
}
|
||||
|
||||
// Mix the colors, choosing the appropriate interpolation method for the given color space
|
||||
fn mix_colors(
|
||||
start_color: vec4<f32>,
|
||||
end_color: vec4<f32>,
|
||||
t: f32,
|
||||
) -> vec4<f32> {
|
||||
#ifdef IN_OKLCH
|
||||
return mix_oklcha(start_color, end_color, t);
|
||||
#else ifdef IN_OKLCH_LONG
|
||||
return mix_linear_rgba_in_oklcha_space_long(start_color, end_color, t);
|
||||
return mix_oklcha_long(start_color, end_color, t);
|
||||
#else ifdef IN_HSV
|
||||
return mix_linear_rgba_in_hsva_space(start_color, end_color, t);
|
||||
return mix_hsva(start_color, end_color, t);
|
||||
#else ifdef IN_HSV_LONG
|
||||
return mix_linear_rgba_in_hsva_space_long(start_color, end_color, t);
|
||||
return mix_hsva_long(start_color, end_color, t);
|
||||
#else ifdef IN_HSL
|
||||
return mix_linear_rgba_in_hsla_space(start_color, end_color, t);
|
||||
return mix_hsla(start_color, end_color, t);
|
||||
#else ifdef IN_HSL_LONG
|
||||
return mix_linear_rgba_in_hsla_space_long(start_color, end_color, t);
|
||||
return mix_hsla_long(start_color, end_color, t);
|
||||
#else
|
||||
// Just lerp in linear RGBA, OkLab and SRGBA spaces
|
||||
return mix(start_color, end_color, t);
|
||||
#endif
|
||||
}
|
||||
|
||||
// Convert a color from the interpolation color space to linear rgba
|
||||
fn convert_to_linear_rgba(
|
||||
color: vec4<f32>,
|
||||
) -> vec4<f32> {
|
||||
#ifdef IN_OKLCH
|
||||
return oklcha_to_linear_rgba(color);
|
||||
#else ifdef IN_OKLCH_LONG
|
||||
return oklcha_to_linear_rgba(color);
|
||||
#else ifdef IN_HSV
|
||||
return hsva_to_linear_rgba(color);
|
||||
#else ifdef IN_HSV_LONG
|
||||
return hsva_to_linear_rgba(color);
|
||||
#else ifdef IN_HSL
|
||||
return hsla_to_linear_rgba(color);
|
||||
#else ifdef IN_HSL_LONG
|
||||
return hsla_to_linear_rgba(color);
|
||||
#else ifdef IN_OKLAB
|
||||
return oklaba_to_linear_rgba(color);
|
||||
#else ifdef IN_SRGB
|
||||
return vec4(pow(color.rgb, vec3(2.2)), color.a);
|
||||
#else
|
||||
// Color is already in linear rgba space
|
||||
return color;
|
||||
#endif
|
||||
}
|
||||
|
@ -540,14 +540,16 @@ fn process_scale_input(
|
||||
|
||||
for (mut transform, selection) in &mut scale_selections {
|
||||
if app_status.selection == *selection {
|
||||
transform.scale *= 1.0 + mouse_motion.delta.x * SCALE_SPEED;
|
||||
transform.scale = (transform.scale * (1.0 + mouse_motion.delta.x * SCALE_SPEED))
|
||||
.clamp(Vec3::splat(0.01), Vec3::splat(5.0));
|
||||
}
|
||||
}
|
||||
|
||||
for (mut spotlight, selection) in &mut spotlight_selections {
|
||||
if app_status.selection == *selection {
|
||||
spotlight.outer_angle =
|
||||
(spotlight.outer_angle * (1.0 + mouse_motion.delta.x * SCALE_SPEED)).min(FRAC_PI_4);
|
||||
spotlight.outer_angle = (spotlight.outer_angle
|
||||
* (1.0 + mouse_motion.delta.x * SCALE_SPEED))
|
||||
.clamp(0.01, FRAC_PI_4);
|
||||
spotlight.inner_angle = spotlight.outer_angle;
|
||||
}
|
||||
}
|
||||
|
@ -510,6 +510,7 @@ Example | Description
|
||||
[Many Foxes](../examples/stress_tests/many_foxes.rs) | Loads an animated fox model and spawns lots of them. Good for testing skinned mesh performance. Takes an unsigned integer argument for the number of foxes to spawn. Defaults to 1000
|
||||
[Many Gizmos](../examples/stress_tests/many_gizmos.rs) | Test rendering of many gizmos
|
||||
[Many Glyphs](../examples/stress_tests/many_glyphs.rs) | Simple benchmark to test text rendering.
|
||||
[Many Gradients](../examples/stress_tests/many_gradients.rs) | Stress test for gradient rendering performance
|
||||
[Many Lights](../examples/stress_tests/many_lights.rs) | Simple benchmark to test rendering many point lights. Run with `WGPU_SETTINGS_PRIO=webgl2` to restrict to uniform buffers and max 256 lights
|
||||
[Many Sprites](../examples/stress_tests/many_sprites.rs) | Displays many sprites in a grid arrangement! Used for performance testing. Use `--colored` to enable color tinted sprites.
|
||||
[Many Text2d](../examples/stress_tests/many_text2d.rs) | Displays many Text2d! Used for performance testing.
|
||||
|
180
examples/stress_tests/many_gradients.rs
Normal file
180
examples/stress_tests/many_gradients.rs
Normal file
@ -0,0 +1,180 @@
|
||||
//! Stress test demonstrating gradient performance improvements.
|
||||
//!
|
||||
//! This example creates many UI nodes with gradients to measure the performance
|
||||
//! impact of pre-converting colors to the target color space on the CPU.
|
||||
|
||||
use argh::FromArgs;
|
||||
use bevy::{
|
||||
color::palettes::css::*,
|
||||
diagnostic::{FrameTimeDiagnosticsPlugin, LogDiagnosticsPlugin},
|
||||
math::ops::sin,
|
||||
prelude::*,
|
||||
ui::{
|
||||
BackgroundGradient, ColorStop, Display, Gradient, InterpolationColorSpace, LinearGradient,
|
||||
RepeatedGridTrack,
|
||||
},
|
||||
window::{PresentMode, WindowResolution},
|
||||
};
|
||||
|
||||
const COLS: usize = 30;
|
||||
|
||||
#[derive(FromArgs, Resource, Debug)]
|
||||
/// Gradient stress test
|
||||
struct Args {
|
||||
/// how many gradients per group (default: 900)
|
||||
#[argh(option, default = "900")]
|
||||
gradient_count: usize,
|
||||
|
||||
/// whether to animate gradients by changing colors
|
||||
#[argh(switch)]
|
||||
animate: bool,
|
||||
|
||||
/// use sRGB interpolation
|
||||
#[argh(switch)]
|
||||
srgb: bool,
|
||||
|
||||
/// use HSL interpolation
|
||||
#[argh(switch)]
|
||||
hsl: bool,
|
||||
}
|
||||
|
||||
fn main() {
|
||||
let args: Args = argh::from_env();
|
||||
let total_gradients = args.gradient_count;
|
||||
|
||||
println!("Gradient stress test with {total_gradients} gradients");
|
||||
println!(
|
||||
"Color space: {}",
|
||||
if args.srgb {
|
||||
"sRGB"
|
||||
} else if args.hsl {
|
||||
"HSL"
|
||||
} else {
|
||||
"OkLab (default)"
|
||||
}
|
||||
);
|
||||
|
||||
App::new()
|
||||
.add_plugins((
|
||||
LogDiagnosticsPlugin::default(),
|
||||
FrameTimeDiagnosticsPlugin::default(),
|
||||
DefaultPlugins.set(WindowPlugin {
|
||||
primary_window: Some(Window {
|
||||
title: "Gradient Stress Test".to_string(),
|
||||
resolution: WindowResolution::new(1920.0, 1080.0),
|
||||
present_mode: PresentMode::AutoNoVsync,
|
||||
..default()
|
||||
}),
|
||||
..default()
|
||||
}),
|
||||
))
|
||||
.insert_resource(args)
|
||||
.add_systems(Startup, setup)
|
||||
.add_systems(Update, animate_gradients)
|
||||
.run();
|
||||
}
|
||||
|
||||
fn setup(mut commands: Commands, args: Res<Args>) {
|
||||
commands.spawn(Camera2d);
|
||||
|
||||
let rows_to_spawn = args.gradient_count.div_ceil(COLS);
|
||||
|
||||
// Create a grid of gradients
|
||||
commands
|
||||
.spawn(Node {
|
||||
width: Val::Percent(100.0),
|
||||
height: Val::Percent(100.0),
|
||||
display: Display::Grid,
|
||||
grid_template_columns: RepeatedGridTrack::flex(COLS as u16, 1.0),
|
||||
grid_template_rows: RepeatedGridTrack::flex(rows_to_spawn as u16, 1.0),
|
||||
..default()
|
||||
})
|
||||
.with_children(|parent| {
|
||||
for i in 0..args.gradient_count {
|
||||
let angle = (i as f32 * 10.0) % 360.0;
|
||||
|
||||
let mut gradient = LinearGradient::new(
|
||||
angle,
|
||||
vec![
|
||||
ColorStop::new(RED, Val::Percent(0.0)),
|
||||
ColorStop::new(BLUE, Val::Percent(100.0)),
|
||||
ColorStop::new(GREEN, Val::Percent(20.0)),
|
||||
ColorStop::new(YELLOW, Val::Percent(40.0)),
|
||||
ColorStop::new(ORANGE, Val::Percent(60.0)),
|
||||
ColorStop::new(LIME, Val::Percent(80.0)),
|
||||
ColorStop::new(DARK_CYAN, Val::Percent(90.0)),
|
||||
],
|
||||
);
|
||||
|
||||
gradient.color_space = if args.srgb {
|
||||
InterpolationColorSpace::Srgba
|
||||
} else if args.hsl {
|
||||
InterpolationColorSpace::Hsla
|
||||
} else {
|
||||
InterpolationColorSpace::Oklaba
|
||||
};
|
||||
|
||||
parent.spawn((
|
||||
Node {
|
||||
width: Val::Percent(100.0),
|
||||
height: Val::Percent(100.0),
|
||||
..default()
|
||||
},
|
||||
BackgroundGradient(vec![Gradient::Linear(gradient)]),
|
||||
GradientNode { index: i },
|
||||
));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
#[derive(Component)]
|
||||
struct GradientNode {
|
||||
index: usize,
|
||||
}
|
||||
|
||||
fn animate_gradients(
|
||||
mut gradients: Query<(&mut BackgroundGradient, &GradientNode)>,
|
||||
args: Res<Args>,
|
||||
time: Res<Time>,
|
||||
) {
|
||||
if !args.animate {
|
||||
return;
|
||||
}
|
||||
|
||||
let t = time.elapsed_secs();
|
||||
|
||||
for (mut bg_gradient, node) in &mut gradients {
|
||||
let offset = node.index as f32 * 0.01;
|
||||
let hue_shift = sin(t + offset) * 0.5 + 0.5;
|
||||
|
||||
if let Some(Gradient::Linear(gradient)) = bg_gradient.0.get_mut(0) {
|
||||
let color1 = Color::hsl(hue_shift * 360.0, 1.0, 0.5);
|
||||
let color2 = Color::hsl((hue_shift + 0.3) * 360.0 % 360.0, 1.0, 0.5);
|
||||
|
||||
gradient.stops = vec![
|
||||
ColorStop::new(color1, Val::Percent(0.0)),
|
||||
ColorStop::new(color2, Val::Percent(100.0)),
|
||||
ColorStop::new(
|
||||
Color::hsl((hue_shift + 0.1) * 360.0 % 360.0, 1.0, 0.5),
|
||||
Val::Percent(20.0),
|
||||
),
|
||||
ColorStop::new(
|
||||
Color::hsl((hue_shift + 0.15) * 360.0 % 360.0, 1.0, 0.5),
|
||||
Val::Percent(40.0),
|
||||
),
|
||||
ColorStop::new(
|
||||
Color::hsl((hue_shift + 0.2) * 360.0 % 360.0, 1.0, 0.5),
|
||||
Val::Percent(60.0),
|
||||
),
|
||||
ColorStop::new(
|
||||
Color::hsl((hue_shift + 0.25) * 360.0 % 360.0, 1.0, 0.5),
|
||||
Val::Percent(80.0),
|
||||
),
|
||||
ColorStop::new(
|
||||
Color::hsl((hue_shift + 0.28) * 360.0 % 360.0, 1.0, 0.5),
|
||||
Val::Percent(90.0),
|
||||
),
|
||||
];
|
||||
}
|
||||
}
|
||||
}
|
10
release-content/migration-guides/rework_merge_mesh_error.md
Normal file
10
release-content/migration-guides/rework_merge_mesh_error.md
Normal file
@ -0,0 +1,10 @@
|
||||
---
|
||||
title: Rework `MergeMeshError`
|
||||
pull_requests: [18561]
|
||||
---
|
||||
|
||||
`MergeMeshError` was reworked to account for the possibility of the meshes being merged having two different `PrimitiveTopology`'s, and was renamed to `MeshMergeError` to align with the naming of other mesh errors.
|
||||
|
||||
- Users will need to rename `MergeMeshError` to `MeshMergeError`
|
||||
- When handling `MergeMeshError` (now `MeshMergeError`), users will need to account for the new `IncompatiblePrimitiveTopology` variant, as it has been changed from a struct to an enum
|
||||
- `Mesh::merge` now returns `Result<(), MeshMergeError>` instead of the previous `Result<(), MergeMeshError>`
|
Loading…
Reference in New Issue
Block a user