Merge remote-tracking branch 'upstream/main' into split-bundle
This commit is contained in:
commit
0309198315
@ -1,6 +1,6 @@
|
||||
use bevy_ecs::prelude::*;
|
||||
|
||||
#[derive(Event, BufferedEvent)]
|
||||
#[derive(BufferedEvent)]
|
||||
struct BenchEvent<const SIZE: usize>([u8; SIZE]);
|
||||
|
||||
pub struct Benchmark<const SIZE: usize>(Events<BenchEvent<SIZE>>);
|
||||
|
@ -1,6 +1,6 @@
|
||||
use bevy_ecs::prelude::*;
|
||||
|
||||
#[derive(Event, BufferedEvent)]
|
||||
#[derive(BufferedEvent)]
|
||||
struct BenchEvent<const SIZE: usize>([u8; SIZE]);
|
||||
|
||||
impl<const SIZE: usize> Default for BenchEvent<SIZE> {
|
||||
|
@ -26,10 +26,7 @@ use accesskit::Node;
|
||||
use bevy_app::Plugin;
|
||||
use bevy_derive::{Deref, DerefMut};
|
||||
use bevy_ecs::{
|
||||
component::Component,
|
||||
event::{BufferedEvent, Event},
|
||||
resource::Resource,
|
||||
schedule::SystemSet,
|
||||
component::Component, event::BufferedEvent, resource::Resource, schedule::SystemSet,
|
||||
};
|
||||
|
||||
#[cfg(feature = "bevy_reflect")]
|
||||
@ -45,7 +42,7 @@ use serde::{Deserialize, Serialize};
|
||||
use bevy_reflect::{ReflectDeserialize, ReflectSerialize};
|
||||
|
||||
/// Wrapper struct for [`accesskit::ActionRequest`]. Required to allow it to be used as an `Event`.
|
||||
#[derive(Event, BufferedEvent, Deref, DerefMut)]
|
||||
#[derive(BufferedEvent, Deref, DerefMut)]
|
||||
#[cfg_attr(feature = "serialize", derive(Serialize, Deserialize))]
|
||||
pub struct ActionRequest(pub accesskit::ActionRequest);
|
||||
|
||||
|
@ -356,7 +356,7 @@ impl App {
|
||||
/// # use bevy_app::prelude::*;
|
||||
/// # use bevy_ecs::prelude::*;
|
||||
/// #
|
||||
/// # #[derive(Event, BufferedEvent)]
|
||||
/// # #[derive(BufferedEvent)]
|
||||
/// # struct MyEvent;
|
||||
/// # let mut app = App::new();
|
||||
/// #
|
||||
@ -1418,7 +1418,7 @@ fn run_once(mut app: App) -> AppExit {
|
||||
/// This type is roughly meant to map to a standard definition of a process exit code (0 means success, not 0 means error). Due to portability concerns
|
||||
/// (see [`ExitCode`](https://doc.rust-lang.org/std/process/struct.ExitCode.html) and [`process::exit`](https://doc.rust-lang.org/std/process/fn.exit.html#))
|
||||
/// we only allow error codes between 1 and [255](u8::MAX).
|
||||
#[derive(Event, BufferedEvent, Debug, Clone, Default, PartialEq, Eq)]
|
||||
#[derive(BufferedEvent, Debug, Clone, Default, PartialEq, Eq)]
|
||||
pub enum AppExit {
|
||||
/// [`App`] exited without any problems.
|
||||
#[default]
|
||||
@ -1486,7 +1486,7 @@ mod tests {
|
||||
change_detection::{DetectChanges, ResMut},
|
||||
component::Component,
|
||||
entity::Entity,
|
||||
event::{BufferedEvent, Event, EventWriter, Events},
|
||||
event::{BufferedEvent, EventWriter, Events},
|
||||
lifecycle::RemovedComponents,
|
||||
query::With,
|
||||
resource::Resource,
|
||||
@ -1852,7 +1852,7 @@ mod tests {
|
||||
}
|
||||
#[test]
|
||||
fn events_should_be_updated_once_per_update() {
|
||||
#[derive(Event, BufferedEvent, Clone)]
|
||||
#[derive(BufferedEvent, Clone)]
|
||||
struct TestEvent;
|
||||
|
||||
let mut app = App::new();
|
||||
|
@ -1,12 +1,12 @@
|
||||
use crate::{Asset, AssetId, AssetLoadError, AssetPath, UntypedAssetId};
|
||||
use bevy_ecs::event::{BufferedEvent, Event};
|
||||
use bevy_ecs::event::BufferedEvent;
|
||||
use bevy_reflect::Reflect;
|
||||
use core::fmt::Debug;
|
||||
|
||||
/// A [`BufferedEvent`] emitted when a specific [`Asset`] fails to load.
|
||||
///
|
||||
/// For an untyped equivalent, see [`UntypedAssetLoadFailedEvent`].
|
||||
#[derive(Event, BufferedEvent, Clone, Debug)]
|
||||
#[derive(BufferedEvent, Clone, Debug)]
|
||||
pub struct AssetLoadFailedEvent<A: Asset> {
|
||||
/// The stable identifier of the asset that failed to load.
|
||||
pub id: AssetId<A>,
|
||||
@ -24,7 +24,7 @@ impl<A: Asset> AssetLoadFailedEvent<A> {
|
||||
}
|
||||
|
||||
/// An untyped version of [`AssetLoadFailedEvent`].
|
||||
#[derive(Event, BufferedEvent, Clone, Debug)]
|
||||
#[derive(BufferedEvent, Clone, Debug)]
|
||||
pub struct UntypedAssetLoadFailedEvent {
|
||||
/// The stable identifier of the asset that failed to load.
|
||||
pub id: UntypedAssetId,
|
||||
@ -46,7 +46,7 @@ impl<A: Asset> From<&AssetLoadFailedEvent<A>> for UntypedAssetLoadFailedEvent {
|
||||
|
||||
/// [`BufferedEvent`]s that occur for a specific loaded [`Asset`], such as "value changed" events and "dependency" events.
|
||||
#[expect(missing_docs, reason = "Documenting the id fields is unhelpful.")]
|
||||
#[derive(Event, BufferedEvent, Reflect)]
|
||||
#[derive(BufferedEvent, Reflect)]
|
||||
pub enum AssetEvent<A: Asset> {
|
||||
/// Emitted whenever an [`Asset`] is added.
|
||||
Added { id: AssetId<A> },
|
||||
|
@ -314,7 +314,7 @@ impl NestedLoader<'_, '_, StaticTyped, Deferred> {
|
||||
} else {
|
||||
self.load_context
|
||||
.asset_server
|
||||
.get_or_create_path_handle(path, None)
|
||||
.get_or_create_path_handle(path, self.meta_transform)
|
||||
};
|
||||
self.load_context.dependencies.insert(handle.id().untyped());
|
||||
handle
|
||||
|
@ -567,6 +567,30 @@ impl Camera {
|
||||
/// To get the world space coordinates with Normalized Device Coordinates, you should use
|
||||
/// [`ndc_to_world`](Self::ndc_to_world).
|
||||
///
|
||||
/// # Example
|
||||
/// ```no_run
|
||||
/// # use bevy_window::Window;
|
||||
/// # use bevy_ecs::prelude::{Single, IntoScheduleConfigs};
|
||||
/// # use bevy_transform::prelude::{GlobalTransform, TransformSystems};
|
||||
/// # use bevy_camera::Camera;
|
||||
/// # use bevy_app::{App, PostUpdate};
|
||||
/// #
|
||||
/// fn system(camera_query: Single<(&Camera, &GlobalTransform)>, window: Single<&Window>) {
|
||||
/// let (camera, camera_transform) = *camera_query;
|
||||
///
|
||||
/// if let Some(cursor_position) = window.cursor_position()
|
||||
/// // Calculate a ray pointing from the camera into the world based on the cursor's position.
|
||||
/// && let Ok(ray) = camera.viewport_to_world(camera_transform, cursor_position)
|
||||
/// {
|
||||
/// println!("{ray:?}");
|
||||
/// }
|
||||
/// }
|
||||
///
|
||||
/// # let mut app = App::new();
|
||||
/// // Run the system after transform propagation so the camera's global transform is up-to-date.
|
||||
/// app.add_systems(PostUpdate, system.after(TransformSystems::Propagate));
|
||||
/// ```
|
||||
///
|
||||
/// # Panics
|
||||
///
|
||||
/// Will panic if the camera's projection matrix is invalid (has a determinant of 0) and
|
||||
@ -605,6 +629,30 @@ impl Camera {
|
||||
/// To get the world space coordinates with Normalized Device Coordinates, you should use
|
||||
/// [`ndc_to_world`](Self::ndc_to_world).
|
||||
///
|
||||
/// # Example
|
||||
/// ```no_run
|
||||
/// # use bevy_window::Window;
|
||||
/// # use bevy_ecs::prelude::*;
|
||||
/// # use bevy_transform::prelude::{GlobalTransform, TransformSystems};
|
||||
/// # use bevy_camera::Camera;
|
||||
/// # use bevy_app::{App, PostUpdate};
|
||||
/// #
|
||||
/// fn system(camera_query: Single<(&Camera, &GlobalTransform)>, window: Single<&Window>) {
|
||||
/// let (camera, camera_transform) = *camera_query;
|
||||
///
|
||||
/// if let Some(cursor_position) = window.cursor_position()
|
||||
/// // Calculate a world position based on the cursor's position.
|
||||
/// && let Ok(world_pos) = camera.viewport_to_world_2d(camera_transform, cursor_position)
|
||||
/// {
|
||||
/// println!("World position: {world_pos:.2}");
|
||||
/// }
|
||||
/// }
|
||||
///
|
||||
/// # let mut app = App::new();
|
||||
/// // Run the system after transform propagation so the camera's global transform is up-to-date.
|
||||
/// app.add_systems(PostUpdate, system.after(TransformSystems::Propagate));
|
||||
/// ```
|
||||
///
|
||||
/// # Panics
|
||||
///
|
||||
/// Will panic if the camera's projection matrix is invalid (has a determinant of 0) and
|
||||
|
@ -36,7 +36,7 @@ impl Plugin for BlitPlugin {
|
||||
|
||||
#[derive(Resource)]
|
||||
pub struct BlitPipeline {
|
||||
pub texture_bind_group: BindGroupLayout,
|
||||
pub layout: BindGroupLayout,
|
||||
pub sampler: Sampler,
|
||||
pub fullscreen_shader: FullscreenShader,
|
||||
pub fragment_shader: Handle<Shader>,
|
||||
@ -46,7 +46,7 @@ impl FromWorld for BlitPipeline {
|
||||
fn from_world(render_world: &mut World) -> Self {
|
||||
let render_device = render_world.resource::<RenderDevice>();
|
||||
|
||||
let texture_bind_group = render_device.create_bind_group_layout(
|
||||
let layout = render_device.create_bind_group_layout(
|
||||
"blit_bind_group_layout",
|
||||
&BindGroupLayoutEntries::sequential(
|
||||
ShaderStages::FRAGMENT,
|
||||
@ -60,7 +60,7 @@ impl FromWorld for BlitPipeline {
|
||||
let sampler = render_device.create_sampler(&SamplerDescriptor::default());
|
||||
|
||||
BlitPipeline {
|
||||
texture_bind_group,
|
||||
layout,
|
||||
sampler,
|
||||
fullscreen_shader: render_world.resource::<FullscreenShader>().clone(),
|
||||
fragment_shader: load_embedded_asset!(render_world, "blit.wgsl"),
|
||||
@ -68,6 +68,20 @@ impl FromWorld for BlitPipeline {
|
||||
}
|
||||
}
|
||||
|
||||
impl BlitPipeline {
|
||||
pub fn create_bind_group(
|
||||
&self,
|
||||
render_device: &RenderDevice,
|
||||
src_texture: &TextureView,
|
||||
) -> BindGroup {
|
||||
render_device.create_bind_group(
|
||||
None,
|
||||
&self.layout,
|
||||
&BindGroupEntries::sequential((src_texture, &self.sampler)),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(PartialEq, Eq, Hash, Clone, Copy)]
|
||||
pub struct BlitPipelineKey {
|
||||
pub texture_format: TextureFormat,
|
||||
@ -81,7 +95,7 @@ impl SpecializedRenderPipeline for BlitPipeline {
|
||||
fn specialize(&self, key: Self::Key) -> RenderPipelineDescriptor {
|
||||
RenderPipelineDescriptor {
|
||||
label: Some("blit pipeline".into()),
|
||||
layout: vec![self.texture_bind_group.clone()],
|
||||
layout: vec![self.layout.clone()],
|
||||
vertex: self.fullscreen_shader.to_vertex_state(),
|
||||
fragment: Some(FragmentState {
|
||||
shader: self.fragment_shader.clone(),
|
||||
|
@ -98,11 +98,8 @@ impl ViewNode for MsaaWritebackNode {
|
||||
occlusion_query_set: None,
|
||||
};
|
||||
|
||||
let bind_group = render_context.render_device().create_bind_group(
|
||||
None,
|
||||
&blit_pipeline.texture_bind_group,
|
||||
&BindGroupEntries::sequential((post_process.source, &blit_pipeline.sampler)),
|
||||
);
|
||||
let bind_group =
|
||||
blit_pipeline.create_bind_group(render_context.render_device(), post_process.source);
|
||||
|
||||
let mut render_pass = render_context
|
||||
.command_encoder()
|
||||
|
@ -3,9 +3,7 @@ use bevy_ecs::{prelude::*, query::QueryItem};
|
||||
use bevy_render::{
|
||||
camera::{CameraOutputMode, ClearColor, ClearColorConfig, ExtractedCamera},
|
||||
render_graph::{NodeRunError, RenderGraphContext, ViewNode},
|
||||
render_resource::{
|
||||
BindGroup, BindGroupEntries, PipelineCache, RenderPassDescriptor, TextureViewId,
|
||||
},
|
||||
render_resource::{BindGroup, PipelineCache, RenderPassDescriptor, TextureViewId},
|
||||
renderer::RenderContext,
|
||||
view::ViewTarget,
|
||||
};
|
||||
@ -30,9 +28,9 @@ impl ViewNode for UpscalingNode {
|
||||
(target, upscaling_target, camera): QueryItem<Self::ViewQuery>,
|
||||
world: &World,
|
||||
) -> Result<(), NodeRunError> {
|
||||
let pipeline_cache = world.get_resource::<PipelineCache>().unwrap();
|
||||
let blit_pipeline = world.get_resource::<BlitPipeline>().unwrap();
|
||||
let clear_color_global = world.get_resource::<ClearColor>().unwrap();
|
||||
let pipeline_cache = world.resource::<PipelineCache>();
|
||||
let blit_pipeline = world.resource::<BlitPipeline>();
|
||||
let clear_color_global = world.resource::<ClearColor>();
|
||||
|
||||
let clear_color = if let Some(camera) = camera {
|
||||
match camera.output_mode {
|
||||
@ -48,19 +46,18 @@ impl ViewNode for UpscalingNode {
|
||||
ClearColorConfig::None => None,
|
||||
};
|
||||
let converted_clear_color = clear_color.map(Into::into);
|
||||
let upscaled_texture = target.main_texture_view();
|
||||
// texture to be upscaled to the output texture
|
||||
let main_texture_view = target.main_texture_view();
|
||||
|
||||
let mut cached_bind_group = self.cached_texture_bind_group.lock().unwrap();
|
||||
let bind_group = match &mut *cached_bind_group {
|
||||
Some((id, bind_group)) if upscaled_texture.id() == *id => bind_group,
|
||||
Some((id, bind_group)) if main_texture_view.id() == *id => bind_group,
|
||||
cached_bind_group => {
|
||||
let bind_group = render_context.render_device().create_bind_group(
|
||||
None,
|
||||
&blit_pipeline.texture_bind_group,
|
||||
&BindGroupEntries::sequential((upscaled_texture, &blit_pipeline.sampler)),
|
||||
);
|
||||
let bind_group = blit_pipeline
|
||||
.create_bind_group(render_context.render_device(), main_texture_view);
|
||||
|
||||
let (_, bind_group) = cached_bind_group.insert((upscaled_texture.id(), bind_group));
|
||||
let (_, bind_group) =
|
||||
cached_bind_group.insert((main_texture_view.id(), bind_group));
|
||||
bind_group
|
||||
}
|
||||
};
|
||||
|
@ -18,9 +18,7 @@ bevy_input_focus = { path = "../bevy_input_focus", version = "0.17.0-dev" }
|
||||
bevy_log = { path = "../bevy_log", version = "0.17.0-dev" }
|
||||
bevy_math = { path = "../bevy_math", version = "0.17.0-dev" }
|
||||
bevy_picking = { path = "../bevy_picking", version = "0.17.0-dev" }
|
||||
bevy_ui = { path = "../bevy_ui", version = "0.17.0-dev", features = [
|
||||
"bevy_ui_picking_backend",
|
||||
] }
|
||||
bevy_ui = { path = "../bevy_ui", version = "0.17.0-dev" }
|
||||
|
||||
# other
|
||||
accesskit = "0.19"
|
||||
|
@ -52,7 +52,7 @@ pub enum CiTestingEvent {
|
||||
}
|
||||
|
||||
/// A custom event that can be configured from a configuration file for CI testing.
|
||||
#[derive(Event, BufferedEvent)]
|
||||
#[derive(BufferedEvent)]
|
||||
pub struct CiTestingCustomEvent(pub String);
|
||||
|
||||
#[cfg(test)]
|
||||
|
@ -285,7 +285,7 @@ They can be sent using the `EventWriter` system parameter and received with `Eve
|
||||
```rust
|
||||
use bevy_ecs::prelude::*;
|
||||
|
||||
#[derive(Event, BufferedEvent)]
|
||||
#[derive(BufferedEvent)]
|
||||
struct Message(String);
|
||||
|
||||
fn writer(mut writer: EventWriter<Message>) {
|
||||
|
@ -37,7 +37,7 @@ fn main() {
|
||||
}
|
||||
|
||||
// This is our event that we will send and receive in systems
|
||||
#[derive(Event, BufferedEvent)]
|
||||
#[derive(BufferedEvent)]
|
||||
struct MyEvent {
|
||||
pub message: String,
|
||||
pub random_value: f32,
|
||||
|
@ -293,8 +293,14 @@ pub fn derive_component(input: TokenStream) -> TokenStream {
|
||||
.then_some(quote! { #bevy_ecs_path::component::Immutable })
|
||||
.unwrap_or(quote! { #bevy_ecs_path::component::Mutable });
|
||||
|
||||
let clone_behavior = if relationship_target.is_some() {
|
||||
quote!(#bevy_ecs_path::component::ComponentCloneBehavior::Custom(#bevy_ecs_path::relationship::clone_relationship_target::<Self>))
|
||||
let clone_behavior = if relationship_target.is_some() || relationship.is_some() {
|
||||
quote!(
|
||||
use #bevy_ecs_path::relationship::{
|
||||
RelationshipCloneBehaviorBase, RelationshipCloneBehaviorViaClone, RelationshipCloneBehaviorViaReflect,
|
||||
RelationshipTargetCloneBehaviorViaClone, RelationshipTargetCloneBehaviorViaReflect, RelationshipTargetCloneBehaviorHierarchy
|
||||
};
|
||||
(&&&&&&&#bevy_ecs_path::relationship::RelationshipCloneBehaviorSpecialization::<Self>::default()).default_clone_behavior()
|
||||
)
|
||||
} else if let Some(behavior) = attrs.clone_behavior {
|
||||
quote!(#bevy_ecs_path::component::ComponentCloneBehavior::#behavior)
|
||||
} else {
|
||||
|
@ -22,6 +22,7 @@ pub(crate) struct BundleRemover<'w> {
|
||||
old_and_new_table: Option<(NonNull<Table>, NonNull<Table>)>,
|
||||
old_archetype: NonNull<Archetype>,
|
||||
new_archetype: NonNull<Archetype>,
|
||||
pub(crate) relationship_hook_mode: RelationshipHookMode,
|
||||
}
|
||||
|
||||
impl<'w> BundleRemover<'w> {
|
||||
@ -97,6 +98,7 @@ impl<'w> BundleRemover<'w> {
|
||||
old_archetype: old_archetype.into(),
|
||||
old_and_new_table: tables,
|
||||
world: world.as_unsafe_world_cell(),
|
||||
relationship_hook_mode: RelationshipHookMode::Run,
|
||||
};
|
||||
if is_new_created {
|
||||
remover
|
||||
@ -160,7 +162,7 @@ impl<'w> BundleRemover<'w> {
|
||||
entity,
|
||||
bundle_components_in_archetype(),
|
||||
caller,
|
||||
RelationshipHookMode::Run,
|
||||
self.relationship_hook_mode,
|
||||
);
|
||||
if self.old_archetype.as_ref().has_remove_observer() {
|
||||
deferred_world.trigger_observers(
|
||||
|
@ -230,7 +230,7 @@ pub trait DetectChangesMut: DetectChanges {
|
||||
/// #[derive(Resource, PartialEq, Eq)]
|
||||
/// pub struct Score(u32);
|
||||
///
|
||||
/// #[derive(Event, BufferedEvent, PartialEq, Eq)]
|
||||
/// #[derive(BufferedEvent, PartialEq, Eq)]
|
||||
/// pub struct ScoreChanged {
|
||||
/// current: u32,
|
||||
/// previous: u32,
|
||||
|
@ -3,16 +3,16 @@ use core::marker::PhantomData;
|
||||
use crate::component::Component;
|
||||
use crate::entity::{ComponentCloneCtx, SourceComponent};
|
||||
|
||||
/// Function type that can be used to clone an entity.
|
||||
/// Function type that can be used to clone a component of an entity.
|
||||
pub type ComponentCloneFn = fn(&SourceComponent, &mut ComponentCloneCtx);
|
||||
|
||||
/// The clone behavior to use when cloning a [`Component`].
|
||||
/// The clone behavior to use when cloning or moving a [`Component`].
|
||||
#[derive(Clone, Debug, Default, PartialEq, Eq)]
|
||||
pub enum ComponentCloneBehavior {
|
||||
/// Uses the default behavior (which is passed to [`ComponentCloneBehavior::resolve`])
|
||||
#[default]
|
||||
Default,
|
||||
/// Do not clone this component.
|
||||
/// Do not clone/move this component.
|
||||
Ignore,
|
||||
/// Uses a custom [`ComponentCloneFn`].
|
||||
Custom(ComponentCloneFn),
|
||||
|
@ -641,6 +641,7 @@ impl Components {
|
||||
///
|
||||
/// # See also
|
||||
///
|
||||
/// * [`ComponentIdFor`](super::ComponentIdFor)
|
||||
/// * [`Components::get_id()`]
|
||||
/// * [`Components::resource_id()`]
|
||||
/// * [`World::component_id()`](crate::world::World::component_id)
|
||||
|
@ -472,58 +472,58 @@ impl<'w> ComponentsQueuedRegistrator<'w> {
|
||||
Self { components, ids }
|
||||
}
|
||||
|
||||
/// Queues this function to run as a component registrator.
|
||||
/// Queues this function to run as a component registrator if the given
|
||||
/// type is not already queued as a component.
|
||||
///
|
||||
/// # Safety
|
||||
///
|
||||
/// The [`TypeId`] must not already be registered or queued as a component.
|
||||
unsafe fn force_register_arbitrary_component(
|
||||
/// The [`TypeId`] must not already be registered as a component.
|
||||
unsafe fn register_arbitrary_component(
|
||||
&self,
|
||||
type_id: TypeId,
|
||||
descriptor: ComponentDescriptor,
|
||||
func: impl FnOnce(&mut ComponentsRegistrator, ComponentId, ComponentDescriptor) + 'static,
|
||||
) -> ComponentId {
|
||||
let id = self.ids.next();
|
||||
self.components
|
||||
.queued
|
||||
.write()
|
||||
.unwrap_or_else(PoisonError::into_inner)
|
||||
.components
|
||||
.insert(
|
||||
type_id,
|
||||
.entry(type_id)
|
||||
.or_insert_with(|| {
|
||||
// SAFETY: The id was just generated.
|
||||
unsafe { QueuedRegistration::new(id, descriptor, func) },
|
||||
);
|
||||
id
|
||||
unsafe { QueuedRegistration::new(self.ids.next(), descriptor, func) }
|
||||
})
|
||||
.id
|
||||
}
|
||||
|
||||
/// Queues this function to run as a resource registrator.
|
||||
/// Queues this function to run as a resource registrator if the given
|
||||
/// type is not already queued as a resource.
|
||||
///
|
||||
/// # Safety
|
||||
///
|
||||
/// The [`TypeId`] must not already be registered or queued as a resource.
|
||||
unsafe fn force_register_arbitrary_resource(
|
||||
/// The [`TypeId`] must not already be registered as a resource.
|
||||
unsafe fn register_arbitrary_resource(
|
||||
&self,
|
||||
type_id: TypeId,
|
||||
descriptor: ComponentDescriptor,
|
||||
func: impl FnOnce(&mut ComponentsRegistrator, ComponentId, ComponentDescriptor) + 'static,
|
||||
) -> ComponentId {
|
||||
let id = self.ids.next();
|
||||
self.components
|
||||
.queued
|
||||
.write()
|
||||
.unwrap_or_else(PoisonError::into_inner)
|
||||
.resources
|
||||
.insert(
|
||||
type_id,
|
||||
.entry(type_id)
|
||||
.or_insert_with(|| {
|
||||
// SAFETY: The id was just generated.
|
||||
unsafe { QueuedRegistration::new(id, descriptor, func) },
|
||||
);
|
||||
id
|
||||
unsafe { QueuedRegistration::new(self.ids.next(), descriptor, func) }
|
||||
})
|
||||
.id
|
||||
}
|
||||
|
||||
/// Queues this function to run as a dynamic registrator.
|
||||
fn force_register_arbitrary_dynamic(
|
||||
fn register_arbitrary_dynamic(
|
||||
&self,
|
||||
descriptor: ComponentDescriptor,
|
||||
func: impl FnOnce(&mut ComponentsRegistrator, ComponentId, ComponentDescriptor) + 'static,
|
||||
@ -554,9 +554,9 @@ impl<'w> ComponentsQueuedRegistrator<'w> {
|
||||
#[inline]
|
||||
pub fn queue_register_component<T: Component>(&self) -> ComponentId {
|
||||
self.component_id::<T>().unwrap_or_else(|| {
|
||||
// SAFETY: We just checked that this type was not in the queue.
|
||||
// SAFETY: We just checked that this type was not already registered.
|
||||
unsafe {
|
||||
self.force_register_arbitrary_component(
|
||||
self.register_arbitrary_component(
|
||||
TypeId::of::<T>(),
|
||||
ComponentDescriptor::new::<T>(),
|
||||
|registrator, id, _descriptor| {
|
||||
@ -584,7 +584,7 @@ impl<'w> ComponentsQueuedRegistrator<'w> {
|
||||
&self,
|
||||
descriptor: ComponentDescriptor,
|
||||
) -> ComponentId {
|
||||
self.force_register_arbitrary_dynamic(descriptor, |registrator, id, descriptor| {
|
||||
self.register_arbitrary_dynamic(descriptor, |registrator, id, descriptor| {
|
||||
// SAFETY: Id uniqueness handled by caller.
|
||||
unsafe {
|
||||
registrator.register_component_inner(id, descriptor);
|
||||
@ -606,9 +606,9 @@ impl<'w> ComponentsQueuedRegistrator<'w> {
|
||||
pub fn queue_register_resource<T: Resource>(&self) -> ComponentId {
|
||||
let type_id = TypeId::of::<T>();
|
||||
self.get_resource_id(type_id).unwrap_or_else(|| {
|
||||
// SAFETY: We just checked that this type was not in the queue.
|
||||
// SAFETY: We just checked that this type was not already registered.
|
||||
unsafe {
|
||||
self.force_register_arbitrary_resource(
|
||||
self.register_arbitrary_resource(
|
||||
type_id,
|
||||
ComponentDescriptor::new_resource::<T>(),
|
||||
move |registrator, id, descriptor| {
|
||||
@ -638,9 +638,9 @@ impl<'w> ComponentsQueuedRegistrator<'w> {
|
||||
pub fn queue_register_non_send<T: Any>(&self) -> ComponentId {
|
||||
let type_id = TypeId::of::<T>();
|
||||
self.get_resource_id(type_id).unwrap_or_else(|| {
|
||||
// SAFETY: We just checked that this type was not in the queue.
|
||||
// SAFETY: We just checked that this type was not already registered.
|
||||
unsafe {
|
||||
self.force_register_arbitrary_resource(
|
||||
self.register_arbitrary_resource(
|
||||
type_id,
|
||||
ComponentDescriptor::new_non_send::<T>(StorageType::default()),
|
||||
move |registrator, id, descriptor| {
|
||||
@ -669,7 +669,7 @@ impl<'w> ComponentsQueuedRegistrator<'w> {
|
||||
&self,
|
||||
descriptor: ComponentDescriptor,
|
||||
) -> ComponentId {
|
||||
self.force_register_arbitrary_dynamic(descriptor, |registrator, id, descriptor| {
|
||||
self.register_arbitrary_dynamic(descriptor, |registrator, id, descriptor| {
|
||||
// SAFETY: Id uniqueness handled by caller.
|
||||
unsafe {
|
||||
registrator.register_component_inner(id, descriptor);
|
||||
|
@ -8,7 +8,8 @@ use derive_more::derive::From;
|
||||
|
||||
use crate::{
|
||||
archetype::Archetype,
|
||||
bundle::{BundleId, InsertMode, StaticBundle},
|
||||
bundle::{BundleId, BundleRemover, InsertMode, StaticBundle},
|
||||
change_detection::MaybeLocation,
|
||||
component::{Component, ComponentCloneBehavior, ComponentCloneFn, ComponentId, ComponentInfo},
|
||||
entity::{hash_map::EntityHashMap, Entities, Entity, EntityMapper},
|
||||
query::DebugCheckedUnwrap,
|
||||
@ -76,6 +77,7 @@ impl<'a> SourceComponent<'a> {
|
||||
pub struct ComponentCloneCtx<'a, 'b> {
|
||||
component_id: ComponentId,
|
||||
target_component_written: bool,
|
||||
target_component_moved: bool,
|
||||
bundle_scratch: &'a mut BundleScratch<'b>,
|
||||
bundle_scratch_allocator: &'b Bump,
|
||||
entities: &'a Entities,
|
||||
@ -117,6 +119,7 @@ impl<'a, 'b> ComponentCloneCtx<'a, 'b> {
|
||||
target,
|
||||
bundle_scratch,
|
||||
target_component_written: false,
|
||||
target_component_moved: false,
|
||||
bundle_scratch_allocator,
|
||||
entities,
|
||||
mapper,
|
||||
@ -131,6 +134,11 @@ impl<'a, 'b> ComponentCloneCtx<'a, 'b> {
|
||||
self.target_component_written
|
||||
}
|
||||
|
||||
/// Returns `true` if used in moving context
|
||||
pub fn moving(&self) -> bool {
|
||||
self.state.move_components
|
||||
}
|
||||
|
||||
/// Returns the current source entity.
|
||||
pub fn source(&self) -> Entity {
|
||||
self.source
|
||||
@ -284,6 +292,12 @@ impl<'a, 'b> ComponentCloneCtx<'a, 'b> {
|
||||
) {
|
||||
self.state.deferred_commands.push_back(Box::new(deferred));
|
||||
}
|
||||
|
||||
/// Marks component as moved and it's `drop` won't run.
|
||||
fn move_component(&mut self) {
|
||||
self.target_component_moved = true;
|
||||
self.target_component_written = true;
|
||||
}
|
||||
}
|
||||
|
||||
/// A configuration determining how to clone entities. This can be built using [`EntityCloner::build_opt_out`]/
|
||||
@ -340,6 +354,19 @@ impl<'a, 'b> ComponentCloneCtx<'a, 'b> {
|
||||
/// 2. component-defined handler using [`Component::clone_behavior`]
|
||||
/// 3. default handler override using [`EntityClonerBuilder::with_default_clone_fn`].
|
||||
/// 4. reflect-based or noop default clone handler depending on if `bevy_reflect` feature is enabled or not.
|
||||
///
|
||||
/// # Moving components
|
||||
/// [`EntityCloner`] can be configured to move components instead of cloning them by using [`EntityClonerBuilder::move_components`].
|
||||
/// In this mode components will be moved - removed from source entity and added to the target entity.
|
||||
///
|
||||
/// Components with [`ComponentCloneBehavior::Ignore`] clone behavior will not be moved, while components that
|
||||
/// have a [`ComponentCloneBehavior::Custom`] clone behavior will be cloned using it and then removed from the source entity.
|
||||
/// All other components will be bitwise copied from the source entity onto the target entity and then removed without dropping.
|
||||
///
|
||||
/// Choosing to move components instead of cloning makes [`EntityClonerBuilder::with_default_clone_fn`] ineffective since it's replaced by
|
||||
/// move handler for components that have [`ComponentCloneBehavior::Default`] clone behavior.
|
||||
///
|
||||
/// Note that moving components still triggers `on_remove` hooks/observers on source entity and `on_insert`/`on_add` hooks/observers on the target entity.
|
||||
#[derive(Default)]
|
||||
pub struct EntityCloner {
|
||||
filter: EntityClonerFilter,
|
||||
@ -391,6 +418,7 @@ impl<'a> BundleScratch<'a> {
|
||||
///
|
||||
/// # Safety
|
||||
/// All [`ComponentId`] values in this instance must come from `world`.
|
||||
#[track_caller]
|
||||
pub(crate) unsafe fn write(
|
||||
self,
|
||||
world: &mut World,
|
||||
@ -527,6 +555,7 @@ impl EntityCloner {
|
||||
}
|
||||
|
||||
/// Clones and inserts components from the `source` entity into the entity mapped by `mapper` from `source` using the stored configuration.
|
||||
#[track_caller]
|
||||
fn clone_entity_internal(
|
||||
state: &mut EntityClonerState,
|
||||
filter: &mut impl CloneByFilter,
|
||||
@ -539,6 +568,8 @@ impl EntityCloner {
|
||||
// PERF: reusing allocated space across clones would be more efficient. Consider an allocation model similar to `Commands`.
|
||||
let bundle_scratch_allocator = Bump::new();
|
||||
let mut bundle_scratch: BundleScratch;
|
||||
let mut moved_components: Vec<ComponentId> = Vec::new();
|
||||
let mut deferred_cloned_component_ids: Vec<ComponentId> = Vec::new();
|
||||
{
|
||||
let world = world.as_unsafe_world_cell();
|
||||
let source_entity = world.get_entity(source).expect("Source entity must exist");
|
||||
@ -564,14 +595,26 @@ impl EntityCloner {
|
||||
.archetype()
|
||||
});
|
||||
|
||||
if state.move_components {
|
||||
moved_components.reserve(source_archetype.component_count());
|
||||
// Replace default handler with special handler which would track if component was moved instead of cloned.
|
||||
// This is later used to determine whether we need to run component's drop function when removing it from the source entity or not.
|
||||
state.default_clone_fn = |_, ctx| ctx.move_component();
|
||||
}
|
||||
|
||||
filter.clone_components(source_archetype, target_archetype, |component| {
|
||||
let handler = match state.clone_behavior_overrides.get(&component) {
|
||||
Some(clone_behavior) => clone_behavior.resolve(state.default_clone_fn),
|
||||
None => world
|
||||
let handler = match state.clone_behavior_overrides.get(&component).or_else(|| {
|
||||
world
|
||||
.components()
|
||||
.get_info(component)
|
||||
.map(|info| info.clone_behavior().resolve(state.default_clone_fn))
|
||||
.unwrap_or(state.default_clone_fn),
|
||||
.map(ComponentInfo::clone_behavior)
|
||||
}) {
|
||||
Some(behavior) => match behavior {
|
||||
ComponentCloneBehavior::Default => state.default_clone_fn,
|
||||
ComponentCloneBehavior::Ignore => return,
|
||||
ComponentCloneBehavior::Custom(custom) => *custom,
|
||||
},
|
||||
None => state.default_clone_fn,
|
||||
};
|
||||
|
||||
// SAFETY: This component exists because it is present on the archetype.
|
||||
@ -607,6 +650,19 @@ impl EntityCloner {
|
||||
};
|
||||
|
||||
(handler)(&source_component, &mut ctx);
|
||||
|
||||
if ctx.state.move_components {
|
||||
if ctx.target_component_moved {
|
||||
moved_components.push(component);
|
||||
}
|
||||
// Component wasn't written by the clone handler, so assume it's going to be
|
||||
// cloned/processed using deferred_commands instead.
|
||||
// This means that it's ComponentId won't be present in BundleScratch's component_ids,
|
||||
// but it should still be removed when move_components is true.
|
||||
else if !ctx.target_component_written() {
|
||||
deferred_cloned_component_ids.push(component);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@ -621,9 +677,67 @@ impl EntityCloner {
|
||||
}
|
||||
|
||||
if state.move_components {
|
||||
world
|
||||
.entity_mut(source)
|
||||
.remove_by_ids(&bundle_scratch.component_ids);
|
||||
let mut source_entity = world.entity_mut(source);
|
||||
|
||||
let cloned_components = if deferred_cloned_component_ids.is_empty() {
|
||||
&bundle_scratch.component_ids
|
||||
} else {
|
||||
// Remove all cloned components with drop by concatenating both vectors
|
||||
deferred_cloned_component_ids.extend(&bundle_scratch.component_ids);
|
||||
&deferred_cloned_component_ids
|
||||
};
|
||||
source_entity.remove_by_ids_with_caller(
|
||||
cloned_components,
|
||||
MaybeLocation::caller(),
|
||||
RelationshipHookMode::RunIfNotLinked,
|
||||
BundleRemover::empty_pre_remove,
|
||||
);
|
||||
|
||||
let table_row = source_entity.location().table_row;
|
||||
|
||||
// Copy moved components and then forget them without calling drop
|
||||
source_entity.remove_by_ids_with_caller(
|
||||
&moved_components,
|
||||
MaybeLocation::caller(),
|
||||
RelationshipHookMode::RunIfNotLinked,
|
||||
|sparse_sets, mut table, components, bundle| {
|
||||
for &component_id in bundle {
|
||||
let Some(component_ptr) = sparse_sets
|
||||
.get(component_id)
|
||||
.and_then(|component| component.get(source))
|
||||
.or_else(|| {
|
||||
// SAFETY: table_row is within this table because we just got it from entity's current location
|
||||
table.as_mut().and_then(|table| unsafe {
|
||||
table.get_component(component_id, table_row)
|
||||
})
|
||||
})
|
||||
else {
|
||||
// Component was removed by some other component's clone side effect before we got to it.
|
||||
continue;
|
||||
};
|
||||
|
||||
// SAFETY: component_id is valid because remove_by_ids_with_caller checked it before calling this closure
|
||||
let info = unsafe { components.get_info_unchecked(component_id) };
|
||||
let layout = info.layout();
|
||||
let target_ptr = bundle_scratch_allocator.alloc_layout(layout);
|
||||
// SAFETY:
|
||||
// - component_ptr points to data with component layout
|
||||
// - target_ptr was just allocated with component layout
|
||||
// - component_ptr and target_ptr don't overlap
|
||||
// - component_ptr matches component_id
|
||||
unsafe {
|
||||
core::ptr::copy_nonoverlapping(
|
||||
component_ptr.as_ptr(),
|
||||
target_ptr.as_ptr(),
|
||||
layout.size(),
|
||||
);
|
||||
bundle_scratch.push_ptr(component_id, PtrMut::new(target_ptr));
|
||||
}
|
||||
}
|
||||
|
||||
(/* should drop? */ false, ())
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
// SAFETY:
|
||||
@ -688,6 +802,8 @@ impl<'w, Filter: CloneByFilter> EntityClonerBuilder<'w, Filter> {
|
||||
}
|
||||
|
||||
/// Sets the default clone function to use.
|
||||
///
|
||||
/// Will be overridden if [`EntityClonerBuilder::move_components`] is enabled.
|
||||
pub fn with_default_clone_fn(&mut self, clone_fn: ComponentCloneFn) -> &mut Self {
|
||||
self.state.default_clone_fn = clone_fn;
|
||||
self
|
||||
@ -700,6 +816,8 @@ impl<'w, Filter: CloneByFilter> EntityClonerBuilder<'w, Filter> {
|
||||
///
|
||||
/// The setting only applies to components that are allowed through the filter
|
||||
/// at the time [`EntityClonerBuilder::clone_entity`] is called.
|
||||
///
|
||||
/// Enabling this overrides any custom function set with [`EntityClonerBuilder::with_default_clone_fn`].
|
||||
pub fn move_components(&mut self, enable: bool) -> &mut Self {
|
||||
self.state.move_components = enable;
|
||||
self
|
||||
@ -1333,8 +1451,9 @@ mod tests {
|
||||
use super::*;
|
||||
use crate::{
|
||||
component::{ComponentDescriptor, StorageType},
|
||||
lifecycle::HookContext,
|
||||
prelude::{ChildOf, Children, Resource},
|
||||
world::{FromWorld, World},
|
||||
world::{DeferredWorld, FromWorld, World},
|
||||
};
|
||||
use bevy_ptr::OwningPtr;
|
||||
use core::marker::PhantomData;
|
||||
@ -2030,10 +2149,21 @@ mod tests {
|
||||
|
||||
assert!(root_children.iter().all(|e| *e != child1 && *e != child2));
|
||||
assert_eq!(root_children.len(), 2);
|
||||
assert_eq!(
|
||||
(
|
||||
world.get::<ChildOf>(root_children[0]),
|
||||
world.get::<ChildOf>(root_children[1])
|
||||
),
|
||||
(Some(&ChildOf(clone_root)), Some(&ChildOf(clone_root)))
|
||||
);
|
||||
let child1_children = world.entity(root_children[0]).get::<Children>().unwrap();
|
||||
assert_eq!(child1_children.len(), 1);
|
||||
assert_ne!(child1_children[0], grandchild);
|
||||
assert!(world.entity(root_children[1]).get::<Children>().is_none());
|
||||
assert_eq!(
|
||||
world.get::<ChildOf>(child1_children[0]),
|
||||
Some(&ChildOf(root_children[0]))
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
world.entity(root).get::<Children>().unwrap().deref(),
|
||||
@ -2062,4 +2192,403 @@ mod tests {
|
||||
assert_eq!(world.entity(e_clone).get::<A>(), Some(&A));
|
||||
assert_eq!(world.entity(e_clone).get::<B>(), Some(&B(1)));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn move_without_clone() {
|
||||
#[derive(Component, PartialEq, Debug)]
|
||||
#[component(storage = "SparseSet")]
|
||||
struct A;
|
||||
|
||||
#[derive(Component, PartialEq, Debug)]
|
||||
struct B(Vec<u8>);
|
||||
|
||||
let mut world = World::default();
|
||||
let e = world.spawn((A, B(alloc::vec![1, 2, 3]))).id();
|
||||
let e_clone = world.spawn_empty().id();
|
||||
let mut builder = EntityCloner::build_opt_out(&mut world);
|
||||
builder.move_components(true);
|
||||
let mut cloner = builder.finish();
|
||||
|
||||
cloner.clone_entity(&mut world, e, e_clone);
|
||||
|
||||
assert_eq!(world.get::<A>(e), None);
|
||||
assert_eq!(world.get::<B>(e), None);
|
||||
|
||||
assert_eq!(world.get::<A>(e_clone), Some(&A));
|
||||
assert_eq!(world.get::<B>(e_clone), Some(&B(alloc::vec![1, 2, 3])));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn move_with_remove_hook() {
|
||||
#[derive(Component, PartialEq, Debug)]
|
||||
#[component(on_remove=remove_hook)]
|
||||
struct B(Option<Vec<u8>>);
|
||||
|
||||
fn remove_hook(mut world: DeferredWorld, ctx: HookContext) {
|
||||
world.get_mut::<B>(ctx.entity).unwrap().0.take();
|
||||
}
|
||||
|
||||
let mut world = World::default();
|
||||
let e = world.spawn(B(Some(alloc::vec![1, 2, 3]))).id();
|
||||
let e_clone = world.spawn_empty().id();
|
||||
let mut builder = EntityCloner::build_opt_out(&mut world);
|
||||
builder.move_components(true);
|
||||
let mut cloner = builder.finish();
|
||||
|
||||
cloner.clone_entity(&mut world, e, e_clone);
|
||||
|
||||
assert_eq!(world.get::<B>(e), None);
|
||||
assert_eq!(world.get::<B>(e_clone), Some(&B(None)));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn move_with_deferred() {
|
||||
#[derive(Component, PartialEq, Debug)]
|
||||
#[component(clone_behavior=Custom(custom))]
|
||||
struct A(u32);
|
||||
|
||||
#[derive(Component, PartialEq, Debug)]
|
||||
struct B(u32);
|
||||
|
||||
fn custom(_src: &SourceComponent, ctx: &mut ComponentCloneCtx) {
|
||||
// Clone using deferred
|
||||
let source = ctx.source();
|
||||
ctx.queue_deferred(move |world, mapper| {
|
||||
let target = mapper.get_mapped(source);
|
||||
world.entity_mut(target).insert(A(10));
|
||||
});
|
||||
}
|
||||
|
||||
let mut world = World::default();
|
||||
let e = world.spawn((A(0), B(1))).id();
|
||||
let e_clone = world.spawn_empty().id();
|
||||
let mut builder = EntityCloner::build_opt_out(&mut world);
|
||||
builder.move_components(true);
|
||||
let mut cloner = builder.finish();
|
||||
|
||||
cloner.clone_entity(&mut world, e, e_clone);
|
||||
|
||||
assert_eq!(world.get::<A>(e), None);
|
||||
assert_eq!(world.get::<A>(e_clone), Some(&A(10)));
|
||||
assert_eq!(world.get::<B>(e), None);
|
||||
assert_eq!(world.get::<B>(e_clone), Some(&B(1)));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn move_relationship() {
|
||||
#[derive(Component, Clone, PartialEq, Eq, Debug)]
|
||||
#[relationship(relationship_target=Target)]
|
||||
struct Source(Entity);
|
||||
|
||||
#[derive(Component, Clone, PartialEq, Eq, Debug)]
|
||||
#[relationship_target(relationship=Source)]
|
||||
struct Target(Vec<Entity>);
|
||||
|
||||
#[derive(Component, PartialEq, Debug)]
|
||||
struct A(u32);
|
||||
|
||||
let mut world = World::default();
|
||||
let e_target = world.spawn(A(1)).id();
|
||||
let e_source = world.spawn((A(2), Source(e_target))).id();
|
||||
|
||||
let mut builder = EntityCloner::build_opt_out(&mut world);
|
||||
builder.move_components(true);
|
||||
let mut cloner = builder.finish();
|
||||
|
||||
let e_source_moved = world.spawn_empty().id();
|
||||
|
||||
cloner.clone_entity(&mut world, e_source, e_source_moved);
|
||||
|
||||
assert_eq!(world.get::<A>(e_source), None);
|
||||
assert_eq!(world.get::<A>(e_source_moved), Some(&A(2)));
|
||||
assert_eq!(world.get::<Source>(e_source), None);
|
||||
assert_eq!(world.get::<Source>(e_source_moved), Some(&Source(e_target)));
|
||||
assert_eq!(
|
||||
world.get::<Target>(e_target),
|
||||
Some(&Target(alloc::vec![e_source_moved]))
|
||||
);
|
||||
|
||||
let e_target_moved = world.spawn_empty().id();
|
||||
|
||||
cloner.clone_entity(&mut world, e_target, e_target_moved);
|
||||
|
||||
assert_eq!(world.get::<A>(e_target), None);
|
||||
assert_eq!(world.get::<A>(e_target_moved), Some(&A(1)));
|
||||
assert_eq!(world.get::<Target>(e_target), None);
|
||||
assert_eq!(
|
||||
world.get::<Target>(e_target_moved),
|
||||
Some(&Target(alloc::vec![e_source_moved]))
|
||||
);
|
||||
assert_eq!(
|
||||
world.get::<Source>(e_source_moved),
|
||||
Some(&Source(e_target_moved))
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn move_hierarchy() {
|
||||
#[derive(Component, PartialEq, Debug)]
|
||||
struct A(u32);
|
||||
|
||||
let mut world = World::default();
|
||||
let e_parent = world.spawn(A(1)).id();
|
||||
let e_child1 = world.spawn((A(2), ChildOf(e_parent))).id();
|
||||
let e_child2 = world.spawn((A(3), ChildOf(e_parent))).id();
|
||||
let e_child1_1 = world.spawn((A(4), ChildOf(e_child1))).id();
|
||||
|
||||
let e_parent_clone = world.spawn_empty().id();
|
||||
|
||||
let mut builder = EntityCloner::build_opt_out(&mut world);
|
||||
builder.move_components(true).linked_cloning(true);
|
||||
let mut cloner = builder.finish();
|
||||
|
||||
cloner.clone_entity(&mut world, e_parent, e_parent_clone);
|
||||
|
||||
assert_eq!(world.get::<A>(e_parent), None);
|
||||
assert_eq!(world.get::<A>(e_child1), None);
|
||||
assert_eq!(world.get::<A>(e_child2), None);
|
||||
assert_eq!(world.get::<A>(e_child1_1), None);
|
||||
|
||||
let mut children = world.get::<Children>(e_parent_clone).unwrap().iter();
|
||||
let e_child1_clone = *children.next().unwrap();
|
||||
let e_child2_clone = *children.next().unwrap();
|
||||
let mut children = world.get::<Children>(e_child1_clone).unwrap().iter();
|
||||
let e_child1_1_clone = *children.next().unwrap();
|
||||
|
||||
assert_eq!(world.get::<A>(e_parent_clone), Some(&A(1)));
|
||||
assert_eq!(world.get::<A>(e_child1_clone), Some(&A(2)));
|
||||
assert_eq!(
|
||||
world.get::<ChildOf>(e_child1_clone),
|
||||
Some(&ChildOf(e_parent_clone))
|
||||
);
|
||||
assert_eq!(world.get::<A>(e_child2_clone), Some(&A(3)));
|
||||
assert_eq!(
|
||||
world.get::<ChildOf>(e_child2_clone),
|
||||
Some(&ChildOf(e_parent_clone))
|
||||
);
|
||||
assert_eq!(world.get::<A>(e_child1_1_clone), Some(&A(4)));
|
||||
assert_eq!(
|
||||
world.get::<ChildOf>(e_child1_1_clone),
|
||||
Some(&ChildOf(e_child1_clone))
|
||||
);
|
||||
}
|
||||
|
||||
// Original: E1 Target{target: [E2], data: [4,5,6]}
|
||||
// | E2 Source{target: E1, data: [1,2,3]}
|
||||
//
|
||||
// Cloned: E3 Target{target: [], data: [4,5,6]}
|
||||
#[test]
|
||||
fn clone_relationship_with_data() {
|
||||
#[derive(Component, Clone)]
|
||||
#[relationship(relationship_target=Target)]
|
||||
struct Source {
|
||||
#[relationship]
|
||||
target: Entity,
|
||||
data: Vec<u8>,
|
||||
}
|
||||
|
||||
#[derive(Component, Clone)]
|
||||
#[relationship_target(relationship=Source)]
|
||||
struct Target {
|
||||
#[relationship]
|
||||
target: Vec<Entity>,
|
||||
data: Vec<u8>,
|
||||
}
|
||||
|
||||
let mut world = World::default();
|
||||
let e_target = world.spawn_empty().id();
|
||||
let e_source = world
|
||||
.spawn(Source {
|
||||
target: e_target,
|
||||
data: alloc::vec![1, 2, 3],
|
||||
})
|
||||
.id();
|
||||
world.get_mut::<Target>(e_target).unwrap().data = alloc::vec![4, 5, 6];
|
||||
|
||||
let builder = EntityCloner::build_opt_out(&mut world);
|
||||
let mut cloner = builder.finish();
|
||||
|
||||
let e_target_clone = world.spawn_empty().id();
|
||||
cloner.clone_entity(&mut world, e_target, e_target_clone);
|
||||
|
||||
let target = world.get::<Target>(e_target).unwrap();
|
||||
let cloned_target = world.get::<Target>(e_target_clone).unwrap();
|
||||
|
||||
assert_eq!(cloned_target.data, target.data);
|
||||
assert_eq!(target.target, alloc::vec![e_source]);
|
||||
assert_eq!(cloned_target.target.len(), 0);
|
||||
|
||||
let source = world.get::<Source>(e_source).unwrap();
|
||||
|
||||
assert_eq!(source.data, alloc::vec![1, 2, 3]);
|
||||
}
|
||||
|
||||
// Original: E1 Target{target: [E2], data: [4,5,6]}
|
||||
// | E2 Source{target: E1, data: [1,2,3]}
|
||||
//
|
||||
// Cloned: E3 Target{target: [E4], data: [4,5,6]}
|
||||
// | E4 Source{target: E3, data: [1,2,3]}
|
||||
#[test]
|
||||
fn clone_linked_relationship_with_data() {
|
||||
#[derive(Component, Clone)]
|
||||
#[relationship(relationship_target=Target)]
|
||||
struct Source {
|
||||
#[relationship]
|
||||
target: Entity,
|
||||
data: Vec<u8>,
|
||||
}
|
||||
|
||||
#[derive(Component, Clone)]
|
||||
#[relationship_target(relationship=Source, linked_spawn)]
|
||||
struct Target {
|
||||
#[relationship]
|
||||
target: Vec<Entity>,
|
||||
data: Vec<u8>,
|
||||
}
|
||||
|
||||
let mut world = World::default();
|
||||
let e_target = world.spawn_empty().id();
|
||||
let e_source = world
|
||||
.spawn(Source {
|
||||
target: e_target,
|
||||
data: alloc::vec![1, 2, 3],
|
||||
})
|
||||
.id();
|
||||
world.get_mut::<Target>(e_target).unwrap().data = alloc::vec![4, 5, 6];
|
||||
|
||||
let mut builder = EntityCloner::build_opt_out(&mut world);
|
||||
builder.linked_cloning(true);
|
||||
let mut cloner = builder.finish();
|
||||
|
||||
let e_target_clone = world.spawn_empty().id();
|
||||
cloner.clone_entity(&mut world, e_target, e_target_clone);
|
||||
|
||||
let target = world.get::<Target>(e_target).unwrap();
|
||||
let cloned_target = world.get::<Target>(e_target_clone).unwrap();
|
||||
|
||||
assert_eq!(cloned_target.data, target.data);
|
||||
assert_eq!(target.target, alloc::vec![e_source]);
|
||||
assert_eq!(cloned_target.target.len(), 1);
|
||||
|
||||
let source = world.get::<Source>(e_source).unwrap();
|
||||
let cloned_source = world.get::<Source>(cloned_target.target[0]).unwrap();
|
||||
|
||||
assert_eq!(cloned_source.data, source.data);
|
||||
assert_eq!(source.target, e_target);
|
||||
assert_eq!(cloned_source.target, e_target_clone);
|
||||
}
|
||||
|
||||
// Original: E1
|
||||
// E2
|
||||
//
|
||||
// Moved: E3 Target{target: [], data: [4,5,6]}
|
||||
#[test]
|
||||
fn move_relationship_with_data() {
|
||||
#[derive(Component, Clone, PartialEq, Eq, Debug)]
|
||||
#[relationship(relationship_target=Target)]
|
||||
struct Source {
|
||||
#[relationship]
|
||||
target: Entity,
|
||||
data: Vec<u8>,
|
||||
}
|
||||
|
||||
#[derive(Component, Clone, PartialEq, Eq, Debug)]
|
||||
#[relationship_target(relationship=Source)]
|
||||
struct Target {
|
||||
#[relationship]
|
||||
target: Vec<Entity>,
|
||||
data: Vec<u8>,
|
||||
}
|
||||
|
||||
let source_data = alloc::vec![1, 2, 3];
|
||||
let target_data = alloc::vec![4, 5, 6];
|
||||
|
||||
let mut world = World::default();
|
||||
let e_target = world.spawn_empty().id();
|
||||
let e_source = world
|
||||
.spawn(Source {
|
||||
target: e_target,
|
||||
data: source_data.clone(),
|
||||
})
|
||||
.id();
|
||||
world.get_mut::<Target>(e_target).unwrap().data = target_data.clone();
|
||||
|
||||
let mut builder = EntityCloner::build_opt_out(&mut world);
|
||||
builder.move_components(true);
|
||||
let mut cloner = builder.finish();
|
||||
|
||||
let e_target_moved = world.spawn_empty().id();
|
||||
cloner.clone_entity(&mut world, e_target, e_target_moved);
|
||||
|
||||
assert_eq!(world.get::<Target>(e_target), None);
|
||||
assert_eq!(
|
||||
world.get::<Source>(e_source),
|
||||
Some(&Source {
|
||||
data: source_data,
|
||||
target: e_target_moved,
|
||||
})
|
||||
);
|
||||
assert_eq!(
|
||||
world.get::<Target>(e_target_moved),
|
||||
Some(&Target {
|
||||
target: alloc::vec![e_source],
|
||||
data: target_data
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
// Original: E1
|
||||
// E2
|
||||
//
|
||||
// Moved: E3 Target{target: [E4], data: [4,5,6]}
|
||||
// | E4 Source{target: E3, data: [1,2,3]}
|
||||
#[test]
|
||||
fn move_linked_relationship_with_data() {
|
||||
#[derive(Component, Clone, PartialEq, Eq, Debug)]
|
||||
#[relationship(relationship_target=Target)]
|
||||
struct Source {
|
||||
#[relationship]
|
||||
target: Entity,
|
||||
data: Vec<u8>,
|
||||
}
|
||||
|
||||
#[derive(Component, Clone, PartialEq, Eq, Debug)]
|
||||
#[relationship_target(relationship=Source, linked_spawn)]
|
||||
struct Target {
|
||||
#[relationship]
|
||||
target: Vec<Entity>,
|
||||
data: Vec<u8>,
|
||||
}
|
||||
|
||||
let source_data = alloc::vec![1, 2, 3];
|
||||
let target_data = alloc::vec![4, 5, 6];
|
||||
|
||||
let mut world = World::default();
|
||||
let e_target = world.spawn_empty().id();
|
||||
let e_source = world
|
||||
.spawn(Source {
|
||||
target: e_target,
|
||||
data: source_data.clone(),
|
||||
})
|
||||
.id();
|
||||
world.get_mut::<Target>(e_target).unwrap().data = target_data.clone();
|
||||
|
||||
let mut builder = EntityCloner::build_opt_out(&mut world);
|
||||
builder.move_components(true).linked_cloning(true);
|
||||
let mut cloner = builder.finish();
|
||||
|
||||
let e_target_moved = world.spawn_empty().id();
|
||||
cloner.clone_entity(&mut world, e_target, e_target_moved);
|
||||
|
||||
assert_eq!(world.get::<Target>(e_target), None);
|
||||
assert_eq!(world.get::<Source>(e_source), None);
|
||||
|
||||
let moved_target = world.get::<Target>(e_target_moved).unwrap();
|
||||
assert_eq!(moved_target.data, target_data);
|
||||
assert_eq!(moved_target.target.len(), 1);
|
||||
|
||||
let moved_source = world.get::<Source>(moved_target.target[0]).unwrap();
|
||||
assert_eq!(moved_source.data, source_data);
|
||||
assert_eq!(moved_source.target, e_target_moved);
|
||||
}
|
||||
}
|
||||
|
@ -259,7 +259,7 @@ pub trait EntityEvent: Event {
|
||||
const AUTO_PROPAGATE: bool = false;
|
||||
}
|
||||
|
||||
/// A buffered [`Event`] for pull-based event handling.
|
||||
/// A buffered event for pull-based event handling.
|
||||
///
|
||||
/// Buffered events can be written with [`EventWriter`] and read using the [`EventReader`] system parameter.
|
||||
/// These events are stored in the [`Events<E>`] resource, and require periodically polling the world for new events,
|
||||
@ -286,7 +286,7 @@ pub trait EntityEvent: Event {
|
||||
/// ```
|
||||
/// # use bevy_ecs::prelude::*;
|
||||
/// #
|
||||
/// #[derive(Event, BufferedEvent)]
|
||||
/// #[derive(BufferedEvent)]
|
||||
/// struct Message(String);
|
||||
/// ```
|
||||
///
|
||||
@ -295,7 +295,7 @@ pub trait EntityEvent: Event {
|
||||
/// ```
|
||||
/// # use bevy_ecs::prelude::*;
|
||||
/// #
|
||||
/// # #[derive(Event, BufferedEvent)]
|
||||
/// # #[derive(BufferedEvent)]
|
||||
/// # struct Message(String);
|
||||
/// #
|
||||
/// fn write_hello(mut writer: EventWriter<Message>) {
|
||||
@ -308,7 +308,7 @@ pub trait EntityEvent: Event {
|
||||
/// ```
|
||||
/// # use bevy_ecs::prelude::*;
|
||||
/// #
|
||||
/// # #[derive(Event, BufferedEvent)]
|
||||
/// # #[derive(BufferedEvent)]
|
||||
/// # struct Message(String);
|
||||
/// #
|
||||
/// fn read_messages(mut reader: EventReader<Message>) {
|
||||
@ -327,9 +327,9 @@ pub trait EntityEvent: Event {
|
||||
#[diagnostic::on_unimplemented(
|
||||
message = "`{Self}` is not an `BufferedEvent`",
|
||||
label = "invalid `BufferedEvent`",
|
||||
note = "consider annotating `{Self}` with `#[derive(Event, BufferedEvent)]`"
|
||||
note = "consider annotating `{Self}` with `#[derive(BufferedEvent)]`"
|
||||
)]
|
||||
pub trait BufferedEvent: Event {}
|
||||
pub trait BufferedEvent: Send + Sync + 'static {}
|
||||
|
||||
/// An internal type that implements [`Component`] for a given [`Event`] type.
|
||||
///
|
||||
|
@ -40,9 +40,9 @@ use {
|
||||
/// # Example
|
||||
///
|
||||
/// ```
|
||||
/// use bevy_ecs::event::{BufferedEvent, Event, Events};
|
||||
/// use bevy_ecs::event::{BufferedEvent, Events};
|
||||
///
|
||||
/// #[derive(Event, BufferedEvent)]
|
||||
/// #[derive(BufferedEvent)]
|
||||
/// struct MyEvent {
|
||||
/// value: usize
|
||||
/// }
|
||||
@ -419,11 +419,11 @@ impl<E: BufferedEvent> ExactSizeIterator for WriteBatchIds<E> {
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::event::{BufferedEvent, Event, Events};
|
||||
use crate::event::{BufferedEvent, Events};
|
||||
|
||||
#[test]
|
||||
fn iter_current_update_events_iterates_over_current_events() {
|
||||
#[derive(Event, BufferedEvent, Clone)]
|
||||
#[derive(BufferedEvent, Clone)]
|
||||
struct TestEvent;
|
||||
|
||||
let mut test_events = Events::<TestEvent>::default();
|
||||
|
@ -22,7 +22,7 @@ use core::marker::PhantomData;
|
||||
/// use bevy_ecs::prelude::*;
|
||||
/// use bevy_ecs::event::{BufferedEvent, Events, EventCursor};
|
||||
///
|
||||
/// #[derive(Event, BufferedEvent, Clone, Debug)]
|
||||
/// #[derive(BufferedEvent, Clone, Debug)]
|
||||
/// struct MyEvent;
|
||||
///
|
||||
/// /// A system that both sends and receives events using a [`Local`] [`EventCursor`].
|
||||
|
@ -41,12 +41,12 @@ mod tests {
|
||||
use bevy_ecs::{event::*, system::assert_is_read_only_system};
|
||||
use bevy_ecs_macros::BufferedEvent;
|
||||
|
||||
#[derive(Event, BufferedEvent, Copy, Clone, PartialEq, Eq, Debug)]
|
||||
#[derive(BufferedEvent, Copy, Clone, PartialEq, Eq, Debug)]
|
||||
struct TestEvent {
|
||||
i: usize,
|
||||
}
|
||||
|
||||
#[derive(Event, BufferedEvent, Clone, PartialEq, Debug, Default)]
|
||||
#[derive(BufferedEvent, Clone, PartialEq, Debug, Default)]
|
||||
struct EmptyTestEvent;
|
||||
|
||||
fn get_events<E: BufferedEvent + Clone>(
|
||||
|
@ -15,7 +15,7 @@ use bevy_ecs::{
|
||||
/// ```
|
||||
/// # use bevy_ecs::prelude::*;
|
||||
///
|
||||
/// #[derive(Event, BufferedEvent, Debug)]
|
||||
/// #[derive(BufferedEvent, Debug)]
|
||||
/// pub struct MyEvent(pub u32); // Custom event type.
|
||||
/// fn my_system(mut reader: EventMutator<MyEvent>) {
|
||||
/// for event in reader.read() {
|
||||
@ -69,7 +69,7 @@ impl<'w, 's, E: BufferedEvent> EventMutator<'w, 's, E> {
|
||||
/// # use bevy_ecs::prelude::*;
|
||||
/// # use std::sync::atomic::{AtomicUsize, Ordering};
|
||||
///
|
||||
/// #[derive(Event, BufferedEvent)]
|
||||
/// #[derive(BufferedEvent)]
|
||||
/// struct MyEvent {
|
||||
/// value: usize,
|
||||
/// }
|
||||
@ -116,7 +116,7 @@ impl<'w, 's, E: BufferedEvent> EventMutator<'w, 's, E> {
|
||||
/// ```
|
||||
/// # use bevy_ecs::prelude::*;
|
||||
/// #
|
||||
/// #[derive(Event, BufferedEvent)]
|
||||
/// #[derive(BufferedEvent)]
|
||||
/// struct CollisionEvent;
|
||||
///
|
||||
/// fn play_collision_sound(mut events: EventMutator<CollisionEvent>) {
|
||||
|
@ -41,7 +41,7 @@ impl<'w, 's, E: BufferedEvent> EventReader<'w, 's, E> {
|
||||
/// # use bevy_ecs::prelude::*;
|
||||
/// # use std::sync::atomic::{AtomicUsize, Ordering};
|
||||
///
|
||||
/// #[derive(Event, BufferedEvent)]
|
||||
/// #[derive(BufferedEvent)]
|
||||
/// struct MyEvent {
|
||||
/// value: usize,
|
||||
/// }
|
||||
@ -88,7 +88,7 @@ impl<'w, 's, E: BufferedEvent> EventReader<'w, 's, E> {
|
||||
/// ```
|
||||
/// # use bevy_ecs::prelude::*;
|
||||
/// #
|
||||
/// #[derive(Event, BufferedEvent)]
|
||||
/// #[derive(BufferedEvent)]
|
||||
/// struct CollisionEvent;
|
||||
///
|
||||
/// fn play_collision_sound(mut events: EventReader<CollisionEvent>) {
|
||||
|
@ -11,7 +11,7 @@ use bevy_ecs::{
|
||||
/// ```
|
||||
/// # use bevy_ecs::prelude::*;
|
||||
///
|
||||
/// #[derive(Event, BufferedEvent)]
|
||||
/// #[derive(BufferedEvent)]
|
||||
/// pub struct MyEvent; // Custom event type.
|
||||
/// fn my_system(mut writer: EventWriter<MyEvent>) {
|
||||
/// writer.write(MyEvent);
|
||||
@ -38,7 +38,7 @@ use bevy_ecs::{
|
||||
///
|
||||
/// ```
|
||||
/// # use bevy_ecs::{prelude::*, event::Events};
|
||||
/// # #[derive(Event, BufferedEvent)]
|
||||
/// # #[derive(BufferedEvent)]
|
||||
/// # pub struct MyEvent;
|
||||
/// fn write_untyped(mut commands: Commands) {
|
||||
/// // Write an event of a specific type without having to declare that
|
||||
|
@ -60,7 +60,7 @@ pub mod world;
|
||||
pub use bevy_ptr as ptr;
|
||||
|
||||
#[cfg(feature = "hotpatching")]
|
||||
use event::{BufferedEvent, Event};
|
||||
use event::BufferedEvent;
|
||||
|
||||
/// The ECS prelude.
|
||||
///
|
||||
@ -140,7 +140,7 @@ pub mod __macro_exports {
|
||||
///
|
||||
/// Systems should refresh their inner pointers.
|
||||
#[cfg(feature = "hotpatching")]
|
||||
#[derive(Event, BufferedEvent, Default)]
|
||||
#[derive(BufferedEvent, Default)]
|
||||
pub struct HotPatched;
|
||||
|
||||
#[cfg(test)]
|
||||
@ -2837,4 +2837,17 @@ mod tests {
|
||||
|
||||
fn custom_clone(_source: &SourceComponent, _ctx: &mut ComponentCloneCtx) {}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn queue_register_component_toctou() {
|
||||
for _ in 0..1000 {
|
||||
let w = World::new();
|
||||
|
||||
std::thread::scope(|s| {
|
||||
let c1 = s.spawn(|| w.components_queue().queue_register_component::<A>());
|
||||
let c2 = s.spawn(|| w.components_queue().queue_register_component::<A>());
|
||||
assert_eq!(c1.join().unwrap(), c2.join().unwrap());
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -393,7 +393,7 @@ pub type OnDespawn = Despawn;
|
||||
|
||||
/// Wrapper around [`Entity`] for [`RemovedComponents`].
|
||||
/// Internally, `RemovedComponents` uses these as an `Events<RemovedComponentEntity>`.
|
||||
#[derive(Event, BufferedEvent, Debug, Clone, Into)]
|
||||
#[derive(BufferedEvent, Debug, Clone, Into)]
|
||||
#[cfg_attr(feature = "bevy_reflect", derive(Reflect))]
|
||||
#[cfg_attr(feature = "bevy_reflect", reflect(Debug, Clone))]
|
||||
pub struct RemovedComponentEntity(Entity);
|
||||
|
@ -4,6 +4,8 @@ mod related_methods;
|
||||
mod relationship_query;
|
||||
mod relationship_source_collection;
|
||||
|
||||
use core::marker::PhantomData;
|
||||
|
||||
use alloc::format;
|
||||
|
||||
use bevy_utils::prelude::DebugName;
|
||||
@ -12,8 +14,8 @@ pub use relationship_query::*;
|
||||
pub use relationship_source_collection::*;
|
||||
|
||||
use crate::{
|
||||
component::{Component, Mutable},
|
||||
entity::{ComponentCloneCtx, Entity, SourceComponent},
|
||||
component::{Component, ComponentCloneBehavior, Mutable},
|
||||
entity::{ComponentCloneCtx, Entity},
|
||||
error::CommandWithEntity,
|
||||
lifecycle::HookContext,
|
||||
world::{DeferredWorld, EntityWorldMut},
|
||||
@ -240,7 +242,19 @@ pub trait RelationshipTarget: Component<Mutability = Mutable> + Sized {
|
||||
|
||||
/// The `on_replace` component hook that maintains the [`Relationship`] / [`RelationshipTarget`] connection.
|
||||
// note: think of this as "on_drop"
|
||||
fn on_replace(mut world: DeferredWorld, HookContext { entity, .. }: HookContext) {
|
||||
fn on_replace(
|
||||
mut world: DeferredWorld,
|
||||
HookContext {
|
||||
entity,
|
||||
relationship_hook_mode,
|
||||
..
|
||||
}: HookContext,
|
||||
) {
|
||||
match relationship_hook_mode {
|
||||
RelationshipHookMode::Run => {}
|
||||
// For RelationshipTarget we don't want to run this hook even if it isn't linked, but for Relationship we do.
|
||||
RelationshipHookMode::Skip | RelationshipHookMode::RunIfNotLinked => return,
|
||||
}
|
||||
let (entities, mut commands) = world.entities_and_commands();
|
||||
let relationship_target = entities.get(entity).unwrap().get::<Self>().unwrap();
|
||||
for source_entity in relationship_target.iter() {
|
||||
@ -287,28 +301,38 @@ pub trait RelationshipTarget: Component<Mutability = Mutable> + Sized {
|
||||
}
|
||||
}
|
||||
|
||||
/// The "clone behavior" for [`RelationshipTarget`]. This actually creates an empty
|
||||
/// [`RelationshipTarget`] instance with space reserved for the number of targets in the
|
||||
/// original instance. The [`RelationshipTarget`] will then be populated with the proper components
|
||||
/// The "clone behavior" for [`RelationshipTarget`]. The [`RelationshipTarget`] will be populated with the proper components
|
||||
/// when the corresponding [`Relationship`] sources of truth are inserted. Cloning the actual entities
|
||||
/// in the original [`RelationshipTarget`] would result in duplicates, so we don't do that!
|
||||
///
|
||||
/// This will also queue up clones of the relationship sources if the [`EntityCloner`](crate::entity::EntityCloner) is configured
|
||||
/// to spawn recursively.
|
||||
pub fn clone_relationship_target<T: RelationshipTarget>(
|
||||
source: &SourceComponent,
|
||||
component: &T,
|
||||
cloned: &mut T,
|
||||
context: &mut ComponentCloneCtx,
|
||||
) {
|
||||
if let Some(component) = source.read::<T>() {
|
||||
let mut cloned = T::with_capacity(component.len());
|
||||
if context.linked_cloning() && T::LINKED_SPAWN {
|
||||
let collection = cloned.collection_mut_risky();
|
||||
for entity in component.iter() {
|
||||
collection.add(entity);
|
||||
context.queue_entity_clone(entity);
|
||||
}
|
||||
if context.linked_cloning() && T::LINKED_SPAWN {
|
||||
let collection = cloned.collection_mut_risky();
|
||||
for entity in component.iter() {
|
||||
collection.add(entity);
|
||||
context.queue_entity_clone(entity);
|
||||
}
|
||||
} else if context.moving() {
|
||||
let target = context.target();
|
||||
let collection = cloned.collection_mut_risky();
|
||||
for entity in component.iter() {
|
||||
collection.add(entity);
|
||||
context.queue_deferred(move |world, _mapper| {
|
||||
// We don't want relationships hooks to run because we are manually constructing the collection here
|
||||
_ = DeferredWorld::from(world)
|
||||
.modify_component_with_relationship_hook_mode::<T::Relationship, ()>(
|
||||
entity,
|
||||
RelationshipHookMode::Skip,
|
||||
|r| r.set_risky(target),
|
||||
);
|
||||
});
|
||||
}
|
||||
context.write_target_component(cloned);
|
||||
}
|
||||
}
|
||||
|
||||
@ -323,6 +347,122 @@ pub enum RelationshipHookMode {
|
||||
Skip,
|
||||
}
|
||||
|
||||
/// Wrapper for components clone specialization using autoderef.
|
||||
#[doc(hidden)]
|
||||
pub struct RelationshipCloneBehaviorSpecialization<T>(PhantomData<T>);
|
||||
|
||||
impl<T> Default for RelationshipCloneBehaviorSpecialization<T> {
|
||||
fn default() -> Self {
|
||||
Self(PhantomData)
|
||||
}
|
||||
}
|
||||
|
||||
/// Base trait for relationship clone specialization using autoderef.
|
||||
#[doc(hidden)]
|
||||
pub trait RelationshipCloneBehaviorBase {
|
||||
fn default_clone_behavior(&self) -> ComponentCloneBehavior;
|
||||
}
|
||||
|
||||
impl<C> RelationshipCloneBehaviorBase for RelationshipCloneBehaviorSpecialization<C> {
|
||||
fn default_clone_behavior(&self) -> ComponentCloneBehavior {
|
||||
// Relationships currently must have `Clone`/`Reflect`-based handler for cloning/moving logic to properly work.
|
||||
ComponentCloneBehavior::Ignore
|
||||
}
|
||||
}
|
||||
|
||||
/// Specialized trait for relationship clone specialization using autoderef.
|
||||
#[doc(hidden)]
|
||||
pub trait RelationshipCloneBehaviorViaReflect {
|
||||
fn default_clone_behavior(&self) -> ComponentCloneBehavior;
|
||||
}
|
||||
|
||||
#[cfg(feature = "bevy_reflect")]
|
||||
impl<C: Relationship + bevy_reflect::Reflect> RelationshipCloneBehaviorViaReflect
|
||||
for &RelationshipCloneBehaviorSpecialization<C>
|
||||
{
|
||||
fn default_clone_behavior(&self) -> ComponentCloneBehavior {
|
||||
ComponentCloneBehavior::reflect()
|
||||
}
|
||||
}
|
||||
|
||||
/// Specialized trait for relationship clone specialization using autoderef.
|
||||
#[doc(hidden)]
|
||||
pub trait RelationshipCloneBehaviorViaClone {
|
||||
fn default_clone_behavior(&self) -> ComponentCloneBehavior;
|
||||
}
|
||||
|
||||
impl<C: Relationship + Clone> RelationshipCloneBehaviorViaClone
|
||||
for &&RelationshipCloneBehaviorSpecialization<C>
|
||||
{
|
||||
fn default_clone_behavior(&self) -> ComponentCloneBehavior {
|
||||
ComponentCloneBehavior::clone::<C>()
|
||||
}
|
||||
}
|
||||
|
||||
/// Specialized trait for relationship target clone specialization using autoderef.
|
||||
#[doc(hidden)]
|
||||
pub trait RelationshipTargetCloneBehaviorViaReflect {
|
||||
fn default_clone_behavior(&self) -> ComponentCloneBehavior;
|
||||
}
|
||||
|
||||
#[cfg(feature = "bevy_reflect")]
|
||||
impl<C: RelationshipTarget + bevy_reflect::Reflect + bevy_reflect::TypePath>
|
||||
RelationshipTargetCloneBehaviorViaReflect for &&&RelationshipCloneBehaviorSpecialization<C>
|
||||
{
|
||||
fn default_clone_behavior(&self) -> ComponentCloneBehavior {
|
||||
ComponentCloneBehavior::Custom(|source, context| {
|
||||
if let Some(component) = source.read::<C>()
|
||||
&& let Ok(mut cloned) = component.reflect_clone_and_take::<C>()
|
||||
{
|
||||
cloned.collection_mut_risky().clear();
|
||||
clone_relationship_target(component, &mut cloned, context);
|
||||
context.write_target_component(cloned);
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/// Specialized trait for relationship target clone specialization using autoderef.
|
||||
#[doc(hidden)]
|
||||
pub trait RelationshipTargetCloneBehaviorViaClone {
|
||||
fn default_clone_behavior(&self) -> ComponentCloneBehavior;
|
||||
}
|
||||
|
||||
impl<C: RelationshipTarget + Clone> RelationshipTargetCloneBehaviorViaClone
|
||||
for &&&&RelationshipCloneBehaviorSpecialization<C>
|
||||
{
|
||||
fn default_clone_behavior(&self) -> ComponentCloneBehavior {
|
||||
ComponentCloneBehavior::Custom(|source, context| {
|
||||
if let Some(component) = source.read::<C>() {
|
||||
let mut cloned = component.clone();
|
||||
cloned.collection_mut_risky().clear();
|
||||
clone_relationship_target(component, &mut cloned, context);
|
||||
context.write_target_component(cloned);
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/// We know there's no additional data on Children, so this handler is an optimization to avoid cloning the entire Collection.
|
||||
#[doc(hidden)]
|
||||
pub trait RelationshipTargetCloneBehaviorHierarchy {
|
||||
fn default_clone_behavior(&self) -> ComponentCloneBehavior;
|
||||
}
|
||||
|
||||
impl RelationshipTargetCloneBehaviorHierarchy
|
||||
for &&&&&RelationshipCloneBehaviorSpecialization<crate::hierarchy::Children>
|
||||
{
|
||||
fn default_clone_behavior(&self) -> ComponentCloneBehavior {
|
||||
ComponentCloneBehavior::Custom(|source, context| {
|
||||
if let Some(component) = source.read::<crate::hierarchy::Children>() {
|
||||
let mut cloned = crate::hierarchy::Children::with_capacity(component.len());
|
||||
clone_relationship_target(component, &mut cloned, context);
|
||||
context.write_target_component(cloned);
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::prelude::{ChildOf, Children};
|
||||
|
@ -864,7 +864,7 @@ pub mod common_conditions {
|
||||
/// my_system.run_if(on_event::<MyEvent>),
|
||||
/// );
|
||||
///
|
||||
/// #[derive(Event, BufferedEvent)]
|
||||
/// #[derive(BufferedEvent)]
|
||||
/// struct MyEvent;
|
||||
///
|
||||
/// fn my_system(mut counter: ResMut<Counter>) {
|
||||
@ -1264,7 +1264,7 @@ where
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::{common_conditions::*, SystemCondition};
|
||||
use crate::event::{BufferedEvent, Event};
|
||||
use crate::event::BufferedEvent;
|
||||
use crate::query::With;
|
||||
use crate::{
|
||||
change_detection::ResMut,
|
||||
@ -1384,7 +1384,7 @@ mod tests {
|
||||
#[derive(Component)]
|
||||
struct TestComponent;
|
||||
|
||||
#[derive(Event, BufferedEvent)]
|
||||
#[derive(BufferedEvent)]
|
||||
struct TestEvent;
|
||||
|
||||
#[derive(Resource)]
|
||||
|
@ -783,7 +783,7 @@ mod tests {
|
||||
#[derive(Component)]
|
||||
struct B;
|
||||
|
||||
#[derive(Event, BufferedEvent)]
|
||||
#[derive(BufferedEvent)]
|
||||
struct E;
|
||||
|
||||
#[derive(Resource, Component)]
|
||||
|
@ -252,6 +252,7 @@ where
|
||||
}
|
||||
|
||||
/// An [`IntoSystem`] creating an instance of [`PipeSystem`].
|
||||
#[derive(Clone)]
|
||||
pub struct IntoPipeSystem<A, B> {
|
||||
a: A,
|
||||
b: B,
|
||||
|
@ -310,8 +310,20 @@ pub fn clone_components<B: StaticBundle>(target: Entity) -> impl EntityCommand {
|
||||
}
|
||||
}
|
||||
|
||||
/// An [`EntityCommand`] that clones the specified components of an entity
|
||||
/// and inserts them into another entity, then removes them from the original entity.
|
||||
/// An [`EntityCommand`] moves the specified components of this entity into another entity.
|
||||
///
|
||||
/// Components with [`Ignore`] clone behavior will not be moved, while components that
|
||||
/// have a [`Custom`] clone behavior will be cloned using it and then removed from the source entity.
|
||||
/// All other components will be moved without any other special handling.
|
||||
///
|
||||
/// Note that this will trigger `on_remove` hooks/observers on this entity and `on_insert`/`on_add` hooks/observers on the target entity.
|
||||
///
|
||||
/// # Panics
|
||||
///
|
||||
/// The command will panic when applied if the target entity does not exist.
|
||||
///
|
||||
/// [`Ignore`]: crate::component::ComponentCloneBehavior::Ignore
|
||||
/// [`Custom`]: crate::component::ComponentCloneBehavior::Custom
|
||||
pub fn move_components<B: StaticBundle>(target: Entity) -> impl EntityCommand {
|
||||
move |mut entity: EntityWorldMut| {
|
||||
entity.move_components::<B>(target);
|
||||
|
@ -2237,15 +2237,20 @@ impl<'a> EntityCommands<'a> {
|
||||
self.queue(entity_command::clone_components::<B>(target))
|
||||
}
|
||||
|
||||
/// Clones the specified components of this entity and inserts them into another entity,
|
||||
/// then removes the components from this entity.
|
||||
/// Moves the specified components of this entity into another entity.
|
||||
///
|
||||
/// Components can only be cloned if they implement
|
||||
/// [`Clone`] or [`Reflect`](bevy_reflect::Reflect).
|
||||
/// Components with [`Ignore`] clone behavior will not be moved, while components that
|
||||
/// have a [`Custom`] clone behavior will be cloned using it and then removed from the source entity.
|
||||
/// All other components will be moved without any other special handling.
|
||||
///
|
||||
/// Note that this will trigger `on_remove` hooks/observers on this entity and `on_insert`/`on_add` hooks/observers on the target entity.
|
||||
///
|
||||
/// # Panics
|
||||
///
|
||||
/// The command will panic when applied if the target entity does not exist.
|
||||
///
|
||||
/// [`Ignore`]: crate::component::ComponentCloneBehavior::Ignore
|
||||
/// [`Custom`]: crate::component::ComponentCloneBehavior::Custom
|
||||
pub fn move_components<B: StaticBundle>(&mut self, target: Entity) -> &mut Self {
|
||||
self.queue(entity_command::move_components::<B>(target))
|
||||
}
|
||||
|
@ -135,7 +135,7 @@ impl SystemMeta {
|
||||
/// # use bevy_ecs::system::SystemState;
|
||||
/// # use bevy_ecs::event::Events;
|
||||
/// #
|
||||
/// # #[derive(Event, BufferedEvent)]
|
||||
/// # #[derive(BufferedEvent)]
|
||||
/// # struct MyEvent;
|
||||
/// # #[derive(Resource)]
|
||||
/// # struct MyResource(u32);
|
||||
@ -168,7 +168,7 @@ impl SystemMeta {
|
||||
/// # use bevy_ecs::system::SystemState;
|
||||
/// # use bevy_ecs::event::Events;
|
||||
/// #
|
||||
/// # #[derive(Event, BufferedEvent)]
|
||||
/// # #[derive(BufferedEvent)]
|
||||
/// # struct MyEvent;
|
||||
/// #[derive(Resource)]
|
||||
/// struct CachedSystemState {
|
||||
|
@ -221,6 +221,10 @@ pub trait System: Send + Sync + 'static {
|
||||
///
|
||||
/// This must only be implemented for system types which do not mutate the `World`
|
||||
/// when [`System::run_unsafe`] is called.
|
||||
#[diagnostic::on_unimplemented(
|
||||
message = "`{Self}` is not a read-only system",
|
||||
label = "invalid read-only system"
|
||||
)]
|
||||
pub unsafe trait ReadOnlySystem: System {
|
||||
/// Runs this system with the given input in the world.
|
||||
///
|
||||
@ -245,6 +249,9 @@ pub unsafe trait ReadOnlySystem: System {
|
||||
/// A convenience type alias for a boxed [`System`] trait object.
|
||||
pub type BoxedSystem<In = (), Out = ()> = Box<dyn System<In = In, Out = Out>>;
|
||||
|
||||
/// A convenience type alias for a boxed [`ReadOnlySystem`] trait object.
|
||||
pub type BoxedReadOnlySystem<In = (), Out = ()> = Box<dyn ReadOnlySystem<In = In, Out = Out>>;
|
||||
|
||||
pub(crate) fn check_system_change_tick(
|
||||
last_run: &mut Tick,
|
||||
check: CheckChangeTicks,
|
||||
|
@ -57,7 +57,7 @@ use variadics_please::{all_tuples, all_tuples_enumerated};
|
||||
/// # use bevy_ecs::prelude::*;
|
||||
/// # #[derive(Resource)]
|
||||
/// # struct SomeResource;
|
||||
/// # #[derive(Event, BufferedEvent)]
|
||||
/// # #[derive(BufferedEvent)]
|
||||
/// # struct SomeEvent;
|
||||
/// # #[derive(Resource)]
|
||||
/// # struct SomeOtherResource;
|
||||
@ -601,7 +601,7 @@ unsafe impl<'w, 's, D: ReadOnlyQueryData + 'static, F: QueryFilter + 'static> Re
|
||||
/// ```
|
||||
/// # use bevy_ecs::prelude::*;
|
||||
/// #
|
||||
/// # #[derive(Event, BufferedEvent)]
|
||||
/// # #[derive(BufferedEvent)]
|
||||
/// # struct MyEvent;
|
||||
/// # impl MyEvent {
|
||||
/// # pub fn new() -> Self { Self }
|
||||
@ -2853,7 +2853,7 @@ impl Display for SystemParamValidationError {
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::{event::Event, system::assert_is_system};
|
||||
use crate::system::assert_is_system;
|
||||
use core::cell::RefCell;
|
||||
|
||||
// Compile test for https://github.com/bevyengine/bevy/pull/2838.
|
||||
@ -3103,7 +3103,7 @@ mod tests {
|
||||
fn missing_event_error() {
|
||||
use crate::prelude::{BufferedEvent, EventReader};
|
||||
|
||||
#[derive(Event, BufferedEvent)]
|
||||
#[derive(BufferedEvent)]
|
||||
pub struct MissingEvent;
|
||||
|
||||
let mut schedule = crate::schedule::Schedule::default();
|
||||
|
@ -19,6 +19,7 @@ use crate::{
|
||||
query::{Access, DebugCheckedUnwrap, ReadOnlyQueryData, ReleaseStateQueryData},
|
||||
relationship::RelationshipHookMode,
|
||||
resource::Resource,
|
||||
storage::{SparseSets, Table},
|
||||
system::IntoObserverSystem,
|
||||
world::{error::EntityComponentError, unsafe_world_cell::UnsafeEntityCell, Mut, Ref, World},
|
||||
};
|
||||
@ -2270,6 +2271,27 @@ impl<'w> EntityWorldMut<'w> {
|
||||
/// entity has been despawned while this `EntityWorldMut` is still alive.
|
||||
#[track_caller]
|
||||
pub fn remove_by_ids(&mut self, component_ids: &[ComponentId]) -> &mut Self {
|
||||
self.remove_by_ids_with_caller(
|
||||
component_ids,
|
||||
MaybeLocation::caller(),
|
||||
RelationshipHookMode::Run,
|
||||
BundleRemover::empty_pre_remove,
|
||||
)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub(crate) fn remove_by_ids_with_caller<T: 'static>(
|
||||
&mut self,
|
||||
component_ids: &[ComponentId],
|
||||
caller: MaybeLocation,
|
||||
relationship_hook_mode: RelationshipHookMode,
|
||||
pre_remove: impl FnOnce(
|
||||
&mut SparseSets,
|
||||
Option<&mut Table>,
|
||||
&Components,
|
||||
&[ComponentId],
|
||||
) -> (bool, T),
|
||||
) -> &mut Self {
|
||||
let location = self.location();
|
||||
let components = &mut self.world.components;
|
||||
|
||||
@ -2285,16 +2307,9 @@ impl<'w> EntityWorldMut<'w> {
|
||||
}) else {
|
||||
return self;
|
||||
};
|
||||
remover.relationship_hook_mode = relationship_hook_mode;
|
||||
// SAFETY: The remover archetype came from the passed location and the removal can not fail.
|
||||
let new_location = unsafe {
|
||||
remover.remove(
|
||||
self.entity,
|
||||
location,
|
||||
MaybeLocation::caller(),
|
||||
BundleRemover::empty_pre_remove,
|
||||
)
|
||||
}
|
||||
.0;
|
||||
let new_location = unsafe { remover.remove(self.entity, location, caller, pre_remove) }.0;
|
||||
|
||||
self.location = Some(new_location);
|
||||
self.world.flush();
|
||||
|
@ -602,6 +602,7 @@ impl World {
|
||||
///
|
||||
/// # See also
|
||||
///
|
||||
/// * [`ComponentIdFor`](crate::component::ComponentIdFor)
|
||||
/// * [`Components::component_id()`]
|
||||
/// * [`Components::get_id()`]
|
||||
#[inline]
|
||||
@ -704,7 +705,7 @@ impl World {
|
||||
/// }
|
||||
/// ```
|
||||
///
|
||||
/// ## [`EntityHashSet`](crate::entity::EntityHashMap)
|
||||
/// ## [`EntityHashSet`](crate::entity::EntityHashSet)
|
||||
///
|
||||
/// ```
|
||||
/// # use bevy_ecs::{prelude::*, entity::EntityHashSet};
|
||||
@ -838,7 +839,7 @@ impl World {
|
||||
/// }
|
||||
/// ```
|
||||
///
|
||||
/// ## [`EntityHashSet`](crate::entity::EntityHashMap)
|
||||
/// ## [`EntityHashSet`](crate::entity::EntityHashSet)
|
||||
///
|
||||
/// ```
|
||||
/// # use bevy_ecs::{prelude::*, entity::EntityHashSet};
|
||||
|
@ -99,7 +99,7 @@ pub fn slider<B: Bundle>(props: SliderProps, overrides: B) -> impl Bundle {
|
||||
ColorStop::new(Color::NONE, Val::Percent(50.)),
|
||||
ColorStop::new(Color::NONE, Val::Percent(100.)),
|
||||
],
|
||||
color_space: InterpolationColorSpace::Srgb,
|
||||
color_space: InterpolationColorSpace::Srgba,
|
||||
})]),
|
||||
overrides,
|
||||
children![(
|
||||
|
@ -58,7 +58,7 @@ pub fn ktx2_buffer_to_image(
|
||||
})?;
|
||||
levels.push(decompressed);
|
||||
}
|
||||
#[cfg(feature = "zstd_rust")]
|
||||
#[cfg(all(feature = "zstd_rust", not(feature = "zstd_c")))]
|
||||
SupercompressionScheme::Zstandard => {
|
||||
let mut cursor = std::io::Cursor::new(level.data);
|
||||
let mut decoder = ruzstd::decoding::StreamingDecoder::new(&mut cursor)
|
||||
@ -71,7 +71,7 @@ pub fn ktx2_buffer_to_image(
|
||||
})?;
|
||||
levels.push(decompressed);
|
||||
}
|
||||
#[cfg(all(feature = "zstd_c", not(feature = "zstd_rust")))]
|
||||
#[cfg(feature = "zstd_c")]
|
||||
SupercompressionScheme::Zstandard => {
|
||||
levels.push(zstd::decode_all(level.data).map_err(|err| {
|
||||
TextureError::SuperDecompressionError(format!(
|
||||
|
@ -10,7 +10,7 @@ use bevy_ecs::{
|
||||
change_detection::DetectChangesMut,
|
||||
component::Component,
|
||||
entity::Entity,
|
||||
event::{BufferedEvent, Event, EventReader, EventWriter},
|
||||
event::{BufferedEvent, EventReader, EventWriter},
|
||||
name::Name,
|
||||
system::{Commands, Query},
|
||||
};
|
||||
@ -32,7 +32,7 @@ use thiserror::Error;
|
||||
/// the in-frame relative ordering of events is important.
|
||||
///
|
||||
/// This event is produced by `bevy_input`.
|
||||
#[derive(Event, BufferedEvent, Debug, Clone, PartialEq, From)]
|
||||
#[derive(BufferedEvent, Debug, Clone, PartialEq, From)]
|
||||
#[cfg_attr(
|
||||
feature = "bevy_reflect",
|
||||
derive(Reflect),
|
||||
@ -59,7 +59,7 @@ pub enum GamepadEvent {
|
||||
/// the in-frame relative ordering of events is important.
|
||||
///
|
||||
/// This event type is used by `bevy_input` to feed its components.
|
||||
#[derive(Event, BufferedEvent, Debug, Clone, PartialEq, From)]
|
||||
#[derive(BufferedEvent, Debug, Clone, PartialEq, From)]
|
||||
#[cfg_attr(
|
||||
feature = "bevy_reflect",
|
||||
derive(Reflect),
|
||||
@ -80,7 +80,7 @@ pub enum RawGamepadEvent {
|
||||
}
|
||||
|
||||
/// [`GamepadButton`] changed event unfiltered by [`GamepadSettings`].
|
||||
#[derive(Event, BufferedEvent, Debug, Copy, Clone, PartialEq)]
|
||||
#[derive(BufferedEvent, Debug, Copy, Clone, PartialEq)]
|
||||
#[cfg_attr(
|
||||
feature = "bevy_reflect",
|
||||
derive(Reflect),
|
||||
@ -112,7 +112,7 @@ impl RawGamepadButtonChangedEvent {
|
||||
}
|
||||
|
||||
/// [`GamepadAxis`] changed event unfiltered by [`GamepadSettings`].
|
||||
#[derive(Event, BufferedEvent, Debug, Copy, Clone, PartialEq)]
|
||||
#[derive(BufferedEvent, Debug, Copy, Clone, PartialEq)]
|
||||
#[cfg_attr(
|
||||
feature = "bevy_reflect",
|
||||
derive(Reflect),
|
||||
@ -145,7 +145,7 @@ impl RawGamepadAxisChangedEvent {
|
||||
|
||||
/// A [`Gamepad`] connection event. Created when a connection to a gamepad
|
||||
/// is established and when a gamepad is disconnected.
|
||||
#[derive(Event, BufferedEvent, Debug, Clone, PartialEq)]
|
||||
#[derive(BufferedEvent, Debug, Clone, PartialEq)]
|
||||
#[cfg_attr(
|
||||
feature = "bevy_reflect",
|
||||
derive(Reflect),
|
||||
@ -184,7 +184,7 @@ impl GamepadConnectionEvent {
|
||||
}
|
||||
|
||||
/// [`GamepadButton`] event triggered by a digital state change.
|
||||
#[derive(Event, BufferedEvent, Debug, Clone, Copy, PartialEq, Eq)]
|
||||
#[derive(BufferedEvent, Debug, Clone, Copy, PartialEq, Eq)]
|
||||
#[cfg_attr(
|
||||
feature = "bevy_reflect",
|
||||
derive(Reflect),
|
||||
@ -216,7 +216,7 @@ impl GamepadButtonStateChangedEvent {
|
||||
}
|
||||
|
||||
/// [`GamepadButton`] event triggered by an analog state change.
|
||||
#[derive(Event, BufferedEvent, Debug, Clone, Copy, PartialEq)]
|
||||
#[derive(BufferedEvent, Debug, Clone, Copy, PartialEq)]
|
||||
#[cfg_attr(
|
||||
feature = "bevy_reflect",
|
||||
derive(Reflect),
|
||||
@ -251,7 +251,7 @@ impl GamepadButtonChangedEvent {
|
||||
}
|
||||
|
||||
/// [`GamepadAxis`] event triggered by an analog state change.
|
||||
#[derive(Event, BufferedEvent, Debug, Clone, Copy, PartialEq)]
|
||||
#[derive(BufferedEvent, Debug, Clone, Copy, PartialEq)]
|
||||
#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
|
||||
#[cfg_attr(
|
||||
feature = "bevy_reflect",
|
||||
@ -1774,7 +1774,7 @@ impl GamepadRumbleIntensity {
|
||||
#[doc(alias = "force feedback")]
|
||||
#[doc(alias = "vibration")]
|
||||
#[doc(alias = "vibrate")]
|
||||
#[derive(Event, BufferedEvent, Clone)]
|
||||
#[derive(BufferedEvent, Clone)]
|
||||
#[cfg_attr(feature = "bevy_reflect", derive(Reflect), reflect(Clone))]
|
||||
pub enum GamepadRumbleRequest {
|
||||
/// Add a rumble to the given gamepad.
|
||||
|
@ -1,6 +1,6 @@
|
||||
//! Gestures functionality, from touchscreens and touchpads.
|
||||
|
||||
use bevy_ecs::event::{BufferedEvent, Event};
|
||||
use bevy_ecs::event::BufferedEvent;
|
||||
use bevy_math::Vec2;
|
||||
#[cfg(feature = "bevy_reflect")]
|
||||
use bevy_reflect::Reflect;
|
||||
@ -17,7 +17,7 @@ use bevy_reflect::{ReflectDeserialize, ReflectSerialize};
|
||||
///
|
||||
/// - Only available on **`macOS`** and **`iOS`**.
|
||||
/// - On **`iOS`**, must be enabled first
|
||||
#[derive(Event, BufferedEvent, Debug, Clone, Copy, PartialEq)]
|
||||
#[derive(BufferedEvent, Debug, Clone, Copy, PartialEq)]
|
||||
#[cfg_attr(
|
||||
feature = "bevy_reflect",
|
||||
derive(Reflect),
|
||||
@ -39,7 +39,7 @@ pub struct PinchGesture(pub f32);
|
||||
///
|
||||
/// - Only available on **`macOS`** and **`iOS`**.
|
||||
/// - On **`iOS`**, must be enabled first
|
||||
#[derive(Event, BufferedEvent, Debug, Clone, Copy, PartialEq)]
|
||||
#[derive(BufferedEvent, Debug, Clone, Copy, PartialEq)]
|
||||
#[cfg_attr(
|
||||
feature = "bevy_reflect",
|
||||
derive(Reflect),
|
||||
@ -58,7 +58,7 @@ pub struct RotationGesture(pub f32);
|
||||
///
|
||||
/// - Only available on **`macOS`** and **`iOS`**.
|
||||
/// - On **`iOS`**, must be enabled first
|
||||
#[derive(Event, BufferedEvent, Debug, Clone, Copy, PartialEq)]
|
||||
#[derive(BufferedEvent, Debug, Clone, Copy, PartialEq)]
|
||||
#[cfg_attr(
|
||||
feature = "bevy_reflect",
|
||||
derive(Reflect),
|
||||
@ -76,7 +76,7 @@ pub struct DoubleTapGesture;
|
||||
/// ## Platform-specific
|
||||
///
|
||||
/// - On **`iOS`**, must be enabled first
|
||||
#[derive(Event, BufferedEvent, Debug, Clone, Copy, PartialEq)]
|
||||
#[derive(BufferedEvent, Debug, Clone, Copy, PartialEq)]
|
||||
#[cfg_attr(
|
||||
feature = "bevy_reflect",
|
||||
derive(Reflect),
|
||||
|
@ -69,7 +69,7 @@ use crate::{ButtonInput, ButtonState};
|
||||
use bevy_ecs::{
|
||||
change_detection::DetectChangesMut,
|
||||
entity::Entity,
|
||||
event::{BufferedEvent, Event, EventReader},
|
||||
event::{BufferedEvent, EventReader},
|
||||
system::ResMut,
|
||||
};
|
||||
|
||||
@ -95,7 +95,7 @@ use bevy_reflect::{ReflectDeserialize, ReflectSerialize};
|
||||
/// The event is consumed inside of the [`keyboard_input_system`] to update the
|
||||
/// [`ButtonInput<KeyCode>`](ButtonInput<KeyCode>) and
|
||||
/// [`ButtonInput<Key>`](ButtonInput<Key>) resources.
|
||||
#[derive(Event, BufferedEvent, Debug, Clone, PartialEq, Eq, Hash)]
|
||||
#[derive(BufferedEvent, Debug, Clone, PartialEq, Eq, Hash)]
|
||||
#[cfg_attr(
|
||||
feature = "bevy_reflect",
|
||||
derive(Reflect),
|
||||
@ -144,7 +144,7 @@ pub struct KeyboardInput {
|
||||
/// when, for example, switching between windows with 'Alt-Tab' or using any other
|
||||
/// OS specific key combination that leads to Bevy window losing focus and not receiving any
|
||||
/// input events
|
||||
#[derive(Event, BufferedEvent, Debug, Clone, PartialEq, Eq)]
|
||||
#[derive(BufferedEvent, Debug, Clone, PartialEq, Eq)]
|
||||
#[cfg_attr(feature = "bevy_reflect", derive(Reflect), reflect(Clone, PartialEq))]
|
||||
#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
|
||||
#[cfg_attr(
|
||||
|
@ -4,7 +4,7 @@ use crate::{ButtonInput, ButtonState};
|
||||
use bevy_ecs::{
|
||||
change_detection::DetectChangesMut,
|
||||
entity::Entity,
|
||||
event::{BufferedEvent, Event, EventReader},
|
||||
event::{BufferedEvent, EventReader},
|
||||
resource::Resource,
|
||||
system::ResMut,
|
||||
};
|
||||
@ -26,7 +26,7 @@ use bevy_reflect::{ReflectDeserialize, ReflectSerialize};
|
||||
///
|
||||
/// The event is read inside of the [`mouse_button_input_system`]
|
||||
/// to update the [`ButtonInput<MouseButton>`] resource.
|
||||
#[derive(Event, BufferedEvent, Debug, Clone, Copy, PartialEq, Eq)]
|
||||
#[derive(BufferedEvent, Debug, Clone, Copy, PartialEq, Eq)]
|
||||
#[cfg_attr(
|
||||
feature = "bevy_reflect",
|
||||
derive(Reflect),
|
||||
@ -91,7 +91,7 @@ pub enum MouseButton {
|
||||
/// However, the event data does not make it possible to distinguish which device it is referring to.
|
||||
///
|
||||
/// [`DeviceEvent::MouseMotion`]: https://docs.rs/winit/latest/winit/event/enum.DeviceEvent.html#variant.MouseMotion
|
||||
#[derive(Event, BufferedEvent, Debug, Clone, Copy, PartialEq)]
|
||||
#[derive(BufferedEvent, Debug, Clone, Copy, PartialEq)]
|
||||
#[cfg_attr(
|
||||
feature = "bevy_reflect",
|
||||
derive(Reflect),
|
||||
@ -140,7 +140,7 @@ pub enum MouseScrollUnit {
|
||||
/// A mouse wheel event.
|
||||
///
|
||||
/// This event is the translated version of the `WindowEvent::MouseWheel` from the `winit` crate.
|
||||
#[derive(Event, BufferedEvent, Debug, Clone, Copy, PartialEq)]
|
||||
#[derive(BufferedEvent, Debug, Clone, Copy, PartialEq)]
|
||||
#[cfg_attr(
|
||||
feature = "bevy_reflect",
|
||||
derive(Reflect),
|
||||
|
@ -2,7 +2,7 @@
|
||||
|
||||
use bevy_ecs::{
|
||||
entity::Entity,
|
||||
event::{BufferedEvent, Event, EventReader},
|
||||
event::{BufferedEvent, EventReader},
|
||||
resource::Resource,
|
||||
system::ResMut,
|
||||
};
|
||||
@ -37,7 +37,7 @@ use bevy_reflect::{ReflectDeserialize, ReflectSerialize};
|
||||
///
|
||||
/// This event is the translated version of the `WindowEvent::Touch` from the `winit` crate.
|
||||
/// It is available to the end user and can be used for game logic.
|
||||
#[derive(Event, BufferedEvent, Debug, Clone, Copy, PartialEq)]
|
||||
#[derive(BufferedEvent, Debug, Clone, Copy, PartialEq)]
|
||||
#[cfg_attr(
|
||||
feature = "bevy_reflect",
|
||||
derive(Reflect),
|
||||
|
@ -201,8 +201,8 @@ impl Plugin for LightPlugin {
|
||||
pub type WithLight = Or<(With<PointLight>, With<SpotLight>, With<DirectionalLight>)>;
|
||||
|
||||
/// Add this component to make a [`Mesh3d`] not cast shadows.
|
||||
#[derive(Debug, Component, Reflect, Default)]
|
||||
#[reflect(Component, Default, Debug)]
|
||||
#[derive(Debug, Component, Reflect, Default, Clone, PartialEq)]
|
||||
#[reflect(Component, Default, Debug, Clone, PartialEq)]
|
||||
pub struct NotShadowCaster;
|
||||
/// Add this component to make a [`Mesh3d`] not receive shadows.
|
||||
///
|
||||
|
@ -224,6 +224,26 @@ impl ShapeSample for Annulus {
|
||||
}
|
||||
}
|
||||
|
||||
impl ShapeSample for Rhombus {
|
||||
type Output = Vec2;
|
||||
|
||||
fn sample_interior<R: Rng + ?Sized>(&self, rng: &mut R) -> Vec2 {
|
||||
let x: f32 = rng.gen_range(0.0..=1.0);
|
||||
let y: f32 = rng.gen_range(0.0..=1.0);
|
||||
|
||||
let unit_p = Vec2::NEG_X + x * Vec2::ONE + Vec2::new(y, -y);
|
||||
unit_p * self.half_diagonals
|
||||
}
|
||||
|
||||
fn sample_boundary<R: Rng + ?Sized>(&self, rng: &mut R) -> Vec2 {
|
||||
let x: f32 = rng.gen_range(-1.0..=1.0);
|
||||
let y_sign = if rng.r#gen() { -1.0 } else { 1.0 };
|
||||
|
||||
let y = (1.0 - ops::abs(x)) * y_sign;
|
||||
Vec2::new(x, y) * self.half_diagonals
|
||||
}
|
||||
}
|
||||
|
||||
impl ShapeSample for Rectangle {
|
||||
type Output = Vec2;
|
||||
|
||||
|
@ -7,7 +7,7 @@ use crate::{
|
||||
};
|
||||
use crate::{DistanceFog, MeshPipelineKey, ViewFogUniformOffset, ViewLightsUniformOffset};
|
||||
use bevy_app::prelude::*;
|
||||
use bevy_asset::{embedded_asset, load_embedded_asset, Handle};
|
||||
use bevy_asset::{embedded_asset, load_embedded_asset, AssetServer, Handle};
|
||||
use bevy_core_pipeline::{
|
||||
core_3d::graph::{Core3d, Node3d},
|
||||
deferred::{
|
||||
@ -19,6 +19,7 @@ use bevy_core_pipeline::{
|
||||
use bevy_ecs::{prelude::*, query::QueryItem};
|
||||
use bevy_image::BevyDefault as _;
|
||||
use bevy_light::{EnvironmentMapLight, ShadowFilteringMethod};
|
||||
use bevy_render::RenderStartup;
|
||||
use bevy_render::{
|
||||
extract_component::{
|
||||
ComponentUniforms, ExtractComponent, ExtractComponentPlugin, UniformComponentPlugin,
|
||||
@ -104,6 +105,7 @@ impl Plugin for DeferredPbrLightingPlugin {
|
||||
|
||||
render_app
|
||||
.init_resource::<SpecializedRenderPipelines<DeferredLightingLayout>>()
|
||||
.add_systems(RenderStartup, init_deferred_lighting_layout)
|
||||
.add_systems(
|
||||
Render,
|
||||
(prepare_deferred_lighting_pipelines.in_set(RenderSystems::Prepare),),
|
||||
@ -121,14 +123,6 @@ impl Plugin for DeferredPbrLightingPlugin {
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
fn finish(&self, app: &mut App) {
|
||||
let Some(render_app) = app.get_sub_app_mut(RenderApp) else {
|
||||
return;
|
||||
};
|
||||
|
||||
render_app.init_resource::<DeferredLightingLayout>();
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
@ -394,22 +388,27 @@ impl SpecializedRenderPipeline for DeferredLightingLayout {
|
||||
}
|
||||
}
|
||||
|
||||
impl FromWorld for DeferredLightingLayout {
|
||||
fn from_world(world: &mut World) -> Self {
|
||||
let render_device = world.resource::<RenderDevice>();
|
||||
let layout = render_device.create_bind_group_layout(
|
||||
"deferred_lighting_layout",
|
||||
&BindGroupLayoutEntries::single(
|
||||
ShaderStages::VERTEX_FRAGMENT,
|
||||
uniform_buffer::<PbrDeferredLightingDepthId>(false),
|
||||
),
|
||||
);
|
||||
Self {
|
||||
mesh_pipeline: world.resource::<MeshPipeline>().clone(),
|
||||
bind_group_layout_2: layout,
|
||||
deferred_lighting_shader: load_embedded_asset!(world, "deferred_lighting.wgsl"),
|
||||
}
|
||||
}
|
||||
pub fn init_deferred_lighting_layout(
|
||||
mut commands: Commands,
|
||||
render_device: Res<RenderDevice>,
|
||||
mesh_pipeline: Res<MeshPipeline>,
|
||||
asset_server: Res<AssetServer>,
|
||||
) {
|
||||
let layout = render_device.create_bind_group_layout(
|
||||
"deferred_lighting_layout",
|
||||
&BindGroupLayoutEntries::single(
|
||||
ShaderStages::VERTEX_FRAGMENT,
|
||||
uniform_buffer::<PbrDeferredLightingDepthId>(false),
|
||||
),
|
||||
);
|
||||
commands.insert_resource(DeferredLightingLayout {
|
||||
mesh_pipeline: mesh_pipeline.clone(),
|
||||
bind_group_layout_2: layout,
|
||||
deferred_lighting_shader: load_embedded_asset!(
|
||||
asset_server.as_ref(),
|
||||
"deferred_lighting.wgsl"
|
||||
),
|
||||
});
|
||||
}
|
||||
|
||||
pub fn insert_deferred_lighting_pass_id_component(
|
||||
|
@ -289,9 +289,7 @@ impl Plugin for LightProbePlugin {
|
||||
load_shader_library!(app, "irradiance_volume.wgsl");
|
||||
|
||||
app.add_plugins(ExtractInstancesPlugin::<EnvironmentMapIds>::new());
|
||||
}
|
||||
|
||||
fn finish(&self, app: &mut App) {
|
||||
let Some(render_app) = app.get_sub_app_mut(RenderApp) else {
|
||||
return;
|
||||
};
|
||||
|
@ -42,8 +42,7 @@ use bevy_ecs::{
|
||||
reflect::ReflectComponent,
|
||||
resource::Resource,
|
||||
schedule::IntoScheduleConfigs,
|
||||
system::{Query, Res, ResMut},
|
||||
world::{FromWorld, World},
|
||||
system::{Commands, Query, Res, ResMut},
|
||||
};
|
||||
use bevy_image::Image;
|
||||
use bevy_math::{uvec2, vec4, Rect, UVec2};
|
||||
@ -57,7 +56,7 @@ use bevy_render::{
|
||||
sync_world::MainEntity,
|
||||
texture::{FallbackImage, GpuImage},
|
||||
view::ViewVisibility,
|
||||
Extract, ExtractSchedule, RenderApp,
|
||||
Extract, ExtractSchedule, RenderApp, RenderStartup,
|
||||
};
|
||||
use bevy_render::{renderer::RenderDevice, sync_world::MainEntityHashMap};
|
||||
use bevy_utils::default;
|
||||
@ -186,17 +185,16 @@ pub struct LightmapSlotIndex(pub(crate) NonMaxU16);
|
||||
impl Plugin for LightmapPlugin {
|
||||
fn build(&self, app: &mut App) {
|
||||
load_shader_library!(app, "lightmap.wgsl");
|
||||
}
|
||||
|
||||
fn finish(&self, app: &mut App) {
|
||||
let Some(render_app) = app.get_sub_app_mut(RenderApp) else {
|
||||
return;
|
||||
};
|
||||
|
||||
render_app.init_resource::<RenderLightmaps>().add_systems(
|
||||
ExtractSchedule,
|
||||
extract_lightmaps.after(MeshExtractionSystems),
|
||||
);
|
||||
render_app
|
||||
.add_systems(RenderStartup, init_render_lightmaps)
|
||||
.add_systems(
|
||||
ExtractSchedule,
|
||||
extract_lightmaps.after(MeshExtractionSystems),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@ -334,21 +332,20 @@ impl Default for Lightmap {
|
||||
}
|
||||
}
|
||||
|
||||
impl FromWorld for RenderLightmaps {
|
||||
fn from_world(world: &mut World) -> Self {
|
||||
let render_device = world.resource::<RenderDevice>();
|
||||
let render_adapter = world.resource::<RenderAdapter>();
|
||||
pub fn init_render_lightmaps(
|
||||
mut commands: Commands,
|
||||
render_device: Res<RenderDevice>,
|
||||
render_adapter: Res<RenderAdapter>,
|
||||
) {
|
||||
let bindless_supported = binding_arrays_are_usable(&render_device, &render_adapter);
|
||||
|
||||
let bindless_supported = binding_arrays_are_usable(render_device, render_adapter);
|
||||
|
||||
RenderLightmaps {
|
||||
render_lightmaps: default(),
|
||||
slabs: vec![],
|
||||
free_slabs: FixedBitSet::new(),
|
||||
pending_lightmaps: default(),
|
||||
bindless_supported,
|
||||
}
|
||||
}
|
||||
commands.insert_resource(RenderLightmaps {
|
||||
render_lightmaps: default(),
|
||||
slabs: vec![],
|
||||
free_slabs: FixedBitSet::new(),
|
||||
pending_lightmaps: default(),
|
||||
bindless_supported,
|
||||
});
|
||||
}
|
||||
|
||||
impl RenderLightmaps {
|
||||
|
@ -267,6 +267,15 @@ impl Plugin for MaterialsPlugin {
|
||||
.init_resource::<LightKeyCache>()
|
||||
.init_resource::<LightSpecializationTicks>()
|
||||
.init_resource::<SpecializedShadowMaterialPipelineCache>()
|
||||
.init_resource::<DrawFunctions<Shadow>>()
|
||||
.init_resource::<RenderMaterialInstances>()
|
||||
.init_resource::<MaterialBindGroupAllocators>()
|
||||
.add_render_command::<Shadow, DrawPrepass>()
|
||||
.add_render_command::<Transmissive3d, DrawMaterial>()
|
||||
.add_render_command::<Transparent3d, DrawMaterial>()
|
||||
.add_render_command::<Opaque3d, DrawMaterial>()
|
||||
.add_render_command::<AlphaMask3d, DrawMaterial>()
|
||||
.add_systems(RenderStartup, init_material_pipeline)
|
||||
.add_systems(
|
||||
Render,
|
||||
(
|
||||
@ -301,21 +310,6 @@ impl Plugin for MaterialsPlugin {
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
fn finish(&self, app: &mut App) {
|
||||
if let Some(render_app) = app.get_sub_app_mut(RenderApp) {
|
||||
render_app
|
||||
.init_resource::<DrawFunctions<Shadow>>()
|
||||
.init_resource::<RenderMaterialInstances>()
|
||||
.init_resource::<MaterialPipeline>()
|
||||
.init_resource::<MaterialBindGroupAllocators>()
|
||||
.add_render_command::<Shadow, DrawPrepass>()
|
||||
.add_render_command::<Transmissive3d, DrawMaterial>()
|
||||
.add_render_command::<Transparent3d, DrawMaterial>()
|
||||
.add_render_command::<Opaque3d, DrawMaterial>()
|
||||
.add_render_command::<AlphaMask3d, DrawMaterial>();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Adds the necessary ECS resources and render logic to enable rendering entities using the given [`Material`]
|
||||
@ -485,12 +479,10 @@ impl SpecializedMeshPipeline for MaterialPipelineSpecializer {
|
||||
}
|
||||
}
|
||||
|
||||
impl FromWorld for MaterialPipeline {
|
||||
fn from_world(world: &mut World) -> Self {
|
||||
MaterialPipeline {
|
||||
mesh_pipeline: world.resource::<MeshPipeline>().clone(),
|
||||
}
|
||||
}
|
||||
pub fn init_material_pipeline(mut commands: Commands, mesh_pipeline: Res<MeshPipeline>) {
|
||||
commands.insert_resource(MaterialPipeline {
|
||||
mesh_pipeline: mesh_pipeline.clone(),
|
||||
});
|
||||
}
|
||||
|
||||
pub type DrawMaterial = (
|
||||
|
@ -2,13 +2,13 @@ mod prepass_bindings;
|
||||
|
||||
use crate::{
|
||||
alpha_mode_pipeline_key, binding_arrays_are_usable, buffer_layout,
|
||||
collect_meshes_for_gpu_building, set_mesh_motion_vector_flags, setup_morph_and_skinning_defs,
|
||||
skin, DeferredDrawFunction, DeferredFragmentShader, DeferredVertexShader, DrawMesh,
|
||||
EntitySpecializationTicks, ErasedMaterialPipelineKey, Material, MaterialPipeline,
|
||||
MaterialProperties, MeshLayouts, MeshPipeline, MeshPipelineKey, OpaqueRendererMethod,
|
||||
PreparedMaterial, PrepassDrawFunction, PrepassFragmentShader, PrepassVertexShader,
|
||||
RenderLightmaps, RenderMaterialInstances, RenderMeshInstanceFlags, RenderMeshInstances,
|
||||
RenderPhaseType, SetMaterialBindGroup, SetMeshBindGroup, ShadowView,
|
||||
collect_meshes_for_gpu_building, init_material_pipeline, set_mesh_motion_vector_flags,
|
||||
setup_morph_and_skinning_defs, skin, DeferredDrawFunction, DeferredFragmentShader,
|
||||
DeferredVertexShader, DrawMesh, EntitySpecializationTicks, ErasedMaterialPipelineKey, Material,
|
||||
MaterialPipeline, MaterialProperties, MeshLayouts, MeshPipeline, MeshPipelineKey,
|
||||
OpaqueRendererMethod, PreparedMaterial, PrepassDrawFunction, PrepassFragmentShader,
|
||||
PrepassVertexShader, RenderLightmaps, RenderMaterialInstances, RenderMeshInstanceFlags,
|
||||
RenderMeshInstances, RenderPhaseType, SetMaterialBindGroup, SetMeshBindGroup, ShadowView,
|
||||
};
|
||||
use bevy_app::{App, Plugin, PreUpdate};
|
||||
use bevy_render::{
|
||||
@ -21,11 +21,11 @@ use bevy_render::{
|
||||
renderer::RenderAdapter,
|
||||
sync_world::RenderEntity,
|
||||
view::{RenderVisibilityRanges, RetainedViewEntity, VISIBILITY_RANGES_STORAGE_BUFFER_COUNT},
|
||||
ExtractSchedule, Render, RenderApp, RenderDebugFlags, RenderSystems,
|
||||
ExtractSchedule, Render, RenderApp, RenderDebugFlags, RenderStartup, RenderSystems,
|
||||
};
|
||||
pub use prepass_bindings::*;
|
||||
|
||||
use bevy_asset::{embedded_asset, load_embedded_asset, Handle};
|
||||
use bevy_asset::{embedded_asset, load_embedded_asset, AssetServer, Handle};
|
||||
use bevy_core_pipeline::{
|
||||
core_3d::CORE_3D_DEPTH_FORMAT, deferred::*, prelude::Camera3d, prepass::*,
|
||||
};
|
||||
@ -87,22 +87,20 @@ impl Plugin for PrepassPipelinePlugin {
|
||||
};
|
||||
|
||||
render_app
|
||||
.add_systems(
|
||||
RenderStartup,
|
||||
(
|
||||
init_prepass_pipeline.after(init_material_pipeline),
|
||||
init_prepass_view_bind_group,
|
||||
)
|
||||
.chain(),
|
||||
)
|
||||
.add_systems(
|
||||
Render,
|
||||
prepare_prepass_view_bind_group.in_set(RenderSystems::PrepareBindGroups),
|
||||
)
|
||||
.init_resource::<SpecializedMeshPipelines<PrepassPipelineSpecializer>>();
|
||||
}
|
||||
|
||||
fn finish(&self, app: &mut App) {
|
||||
let Some(render_app) = app.get_sub_app_mut(RenderApp) else {
|
||||
return;
|
||||
};
|
||||
|
||||
render_app
|
||||
.init_resource::<PrepassPipeline>()
|
||||
.init_resource::<PrepassViewBindGroup>();
|
||||
}
|
||||
}
|
||||
|
||||
/// Sets up the prepasses for a material.
|
||||
@ -273,78 +271,79 @@ pub struct PrepassPipeline {
|
||||
pub material_pipeline: MaterialPipeline,
|
||||
}
|
||||
|
||||
impl FromWorld for PrepassPipeline {
|
||||
fn from_world(world: &mut World) -> Self {
|
||||
let render_device = world.resource::<RenderDevice>();
|
||||
let render_adapter = world.resource::<RenderAdapter>();
|
||||
let visibility_ranges_buffer_binding_type = render_device
|
||||
.get_supported_read_only_binding_type(VISIBILITY_RANGES_STORAGE_BUFFER_COUNT);
|
||||
pub fn init_prepass_pipeline(
|
||||
mut commands: Commands,
|
||||
render_device: Res<RenderDevice>,
|
||||
render_adapter: Res<RenderAdapter>,
|
||||
mesh_pipeline: Res<MeshPipeline>,
|
||||
material_pipeline: Res<MaterialPipeline>,
|
||||
asset_server: Res<AssetServer>,
|
||||
) {
|
||||
let visibility_ranges_buffer_binding_type =
|
||||
render_device.get_supported_read_only_binding_type(VISIBILITY_RANGES_STORAGE_BUFFER_COUNT);
|
||||
|
||||
let view_layout_motion_vectors = render_device.create_bind_group_layout(
|
||||
"prepass_view_layout_motion_vectors",
|
||||
&BindGroupLayoutEntries::with_indices(
|
||||
ShaderStages::VERTEX_FRAGMENT,
|
||||
let view_layout_motion_vectors = render_device.create_bind_group_layout(
|
||||
"prepass_view_layout_motion_vectors",
|
||||
&BindGroupLayoutEntries::with_indices(
|
||||
ShaderStages::VERTEX_FRAGMENT,
|
||||
(
|
||||
// View
|
||||
(0, uniform_buffer::<ViewUniform>(true)),
|
||||
// Globals
|
||||
(1, uniform_buffer::<GlobalsUniform>(false)),
|
||||
// PreviousViewUniforms
|
||||
(2, uniform_buffer::<PreviousViewData>(true)),
|
||||
// VisibilityRanges
|
||||
(
|
||||
// View
|
||||
(0, uniform_buffer::<ViewUniform>(true)),
|
||||
// Globals
|
||||
(1, uniform_buffer::<GlobalsUniform>(false)),
|
||||
// PreviousViewUniforms
|
||||
(2, uniform_buffer::<PreviousViewData>(true)),
|
||||
// VisibilityRanges
|
||||
(
|
||||
14,
|
||||
buffer_layout(
|
||||
visibility_ranges_buffer_binding_type,
|
||||
false,
|
||||
Some(Vec4::min_size()),
|
||||
)
|
||||
.visibility(ShaderStages::VERTEX),
|
||||
),
|
||||
14,
|
||||
buffer_layout(
|
||||
visibility_ranges_buffer_binding_type,
|
||||
false,
|
||||
Some(Vec4::min_size()),
|
||||
)
|
||||
.visibility(ShaderStages::VERTEX),
|
||||
),
|
||||
),
|
||||
);
|
||||
),
|
||||
);
|
||||
|
||||
let view_layout_no_motion_vectors = render_device.create_bind_group_layout(
|
||||
"prepass_view_layout_no_motion_vectors",
|
||||
&BindGroupLayoutEntries::with_indices(
|
||||
ShaderStages::VERTEX_FRAGMENT,
|
||||
let view_layout_no_motion_vectors = render_device.create_bind_group_layout(
|
||||
"prepass_view_layout_no_motion_vectors",
|
||||
&BindGroupLayoutEntries::with_indices(
|
||||
ShaderStages::VERTEX_FRAGMENT,
|
||||
(
|
||||
// View
|
||||
(0, uniform_buffer::<ViewUniform>(true)),
|
||||
// Globals
|
||||
(1, uniform_buffer::<GlobalsUniform>(false)),
|
||||
// VisibilityRanges
|
||||
(
|
||||
// View
|
||||
(0, uniform_buffer::<ViewUniform>(true)),
|
||||
// Globals
|
||||
(1, uniform_buffer::<GlobalsUniform>(false)),
|
||||
// VisibilityRanges
|
||||
(
|
||||
14,
|
||||
buffer_layout(
|
||||
visibility_ranges_buffer_binding_type,
|
||||
false,
|
||||
Some(Vec4::min_size()),
|
||||
)
|
||||
.visibility(ShaderStages::VERTEX),
|
||||
),
|
||||
14,
|
||||
buffer_layout(
|
||||
visibility_ranges_buffer_binding_type,
|
||||
false,
|
||||
Some(Vec4::min_size()),
|
||||
)
|
||||
.visibility(ShaderStages::VERTEX),
|
||||
),
|
||||
),
|
||||
);
|
||||
),
|
||||
);
|
||||
|
||||
let mesh_pipeline = world.resource::<MeshPipeline>();
|
||||
|
||||
let depth_clip_control_supported = render_device
|
||||
.features()
|
||||
.contains(WgpuFeatures::DEPTH_CLIP_CONTROL);
|
||||
PrepassPipeline {
|
||||
view_layout_motion_vectors,
|
||||
view_layout_no_motion_vectors,
|
||||
mesh_layouts: mesh_pipeline.mesh_layouts.clone(),
|
||||
default_prepass_shader: load_embedded_asset!(world, "prepass.wgsl"),
|
||||
skins_use_uniform_buffers: skin::skins_use_uniform_buffers(render_device),
|
||||
depth_clip_control_supported,
|
||||
binding_arrays_are_usable: binding_arrays_are_usable(render_device, render_adapter),
|
||||
empty_layout: render_device.create_bind_group_layout("prepass_empty_layout", &[]),
|
||||
material_pipeline: world.resource::<MaterialPipeline>().clone(),
|
||||
}
|
||||
}
|
||||
let depth_clip_control_supported = render_device
|
||||
.features()
|
||||
.contains(WgpuFeatures::DEPTH_CLIP_CONTROL);
|
||||
commands.insert_resource(PrepassPipeline {
|
||||
view_layout_motion_vectors,
|
||||
view_layout_no_motion_vectors,
|
||||
mesh_layouts: mesh_pipeline.mesh_layouts.clone(),
|
||||
default_prepass_shader: load_embedded_asset!(asset_server.as_ref(), "prepass.wgsl"),
|
||||
skins_use_uniform_buffers: skin::skins_use_uniform_buffers(&render_device),
|
||||
depth_clip_control_supported,
|
||||
binding_arrays_are_usable: binding_arrays_are_usable(&render_device, &render_adapter),
|
||||
empty_layout: render_device.create_bind_group_layout("prepass_empty_layout", &[]),
|
||||
material_pipeline: material_pipeline.clone(),
|
||||
});
|
||||
}
|
||||
|
||||
pub struct PrepassPipelineSpecializer {
|
||||
@ -702,22 +701,21 @@ pub struct PrepassViewBindGroup {
|
||||
pub empty_bind_group: BindGroup,
|
||||
}
|
||||
|
||||
impl FromWorld for PrepassViewBindGroup {
|
||||
fn from_world(world: &mut World) -> Self {
|
||||
let pipeline = world.resource::<PrepassPipeline>();
|
||||
|
||||
let render_device = world.resource::<RenderDevice>();
|
||||
let empty_bind_group = render_device.create_bind_group(
|
||||
"prepass_view_empty_bind_group",
|
||||
&pipeline.empty_layout,
|
||||
&[],
|
||||
);
|
||||
PrepassViewBindGroup {
|
||||
motion_vectors: None,
|
||||
no_motion_vectors: None,
|
||||
empty_bind_group,
|
||||
}
|
||||
}
|
||||
pub fn init_prepass_view_bind_group(
|
||||
mut commands: Commands,
|
||||
render_device: Res<RenderDevice>,
|
||||
pipeline: Res<PrepassPipeline>,
|
||||
) {
|
||||
let empty_bind_group = render_device.create_bind_group(
|
||||
"prepass_view_empty_bind_group",
|
||||
&pipeline.empty_layout,
|
||||
&[],
|
||||
);
|
||||
commands.insert_resource(PrepassViewBindGroup {
|
||||
motion_vectors: None,
|
||||
no_motion_vectors: None,
|
||||
empty_bind_group,
|
||||
});
|
||||
}
|
||||
|
||||
pub fn prepare_prepass_view_bind_group(
|
||||
|
@ -66,8 +66,7 @@ struct Lights {
|
||||
cluster_factors: vec4<f32>,
|
||||
n_directional_lights: u32,
|
||||
spot_light_shadowmap_offset: i32,
|
||||
environment_map_smallest_specular_mip_level: u32,
|
||||
environment_map_intensity: f32,
|
||||
ambient_light_affects_lightmapped_meshes: u32
|
||||
};
|
||||
|
||||
struct Fog {
|
||||
|
@ -604,21 +604,6 @@ fn apply_pbr_lighting(
|
||||
|
||||
// Environment map light (indirect)
|
||||
#ifdef ENVIRONMENT_MAP
|
||||
|
||||
#ifdef STANDARD_MATERIAL_ANISOTROPY
|
||||
var bent_normal_lighting_input = lighting_input;
|
||||
bend_normal_for_anisotropy(&bent_normal_lighting_input);
|
||||
let environment_map_lighting_input = &bent_normal_lighting_input;
|
||||
#else // STANDARD_MATERIAL_ANISOTROPY
|
||||
let environment_map_lighting_input = &lighting_input;
|
||||
#endif // STANDARD_MATERIAL_ANISOTROPY
|
||||
|
||||
let environment_light = environment_map::environment_map_light(
|
||||
environment_map_lighting_input,
|
||||
&clusterable_object_index_ranges,
|
||||
found_diffuse_indirect,
|
||||
);
|
||||
|
||||
// If screen space reflections are going to be used for this material, don't
|
||||
// accumulate environment map light yet. The SSR shader will do it.
|
||||
#ifdef SCREEN_SPACE_REFLECTIONS
|
||||
@ -627,22 +612,38 @@ fn apply_pbr_lighting(
|
||||
#else // SCREEN_SPACE_REFLECTIONS
|
||||
let use_ssr = false;
|
||||
#endif // SCREEN_SPACE_REFLECTIONS
|
||||
|
||||
|
||||
if (!use_ssr) {
|
||||
#ifdef STANDARD_MATERIAL_ANISOTROPY
|
||||
var bent_normal_lighting_input = lighting_input;
|
||||
bend_normal_for_anisotropy(&bent_normal_lighting_input);
|
||||
let environment_map_lighting_input = &bent_normal_lighting_input;
|
||||
#else // STANDARD_MATERIAL_ANISOTROPY
|
||||
let environment_map_lighting_input = &lighting_input;
|
||||
#endif // STANDARD_MATERIAL_ANISOTROPY
|
||||
|
||||
let environment_light = environment_map::environment_map_light(
|
||||
&lighting_input,
|
||||
environment_map_lighting_input,
|
||||
&clusterable_object_index_ranges,
|
||||
found_diffuse_indirect
|
||||
found_diffuse_indirect,
|
||||
);
|
||||
|
||||
indirect_light += environment_light.diffuse * diffuse_occlusion +
|
||||
environment_light.specular * specular_occlusion;
|
||||
}
|
||||
|
||||
#endif // ENVIRONMENT_MAP
|
||||
|
||||
// Ambient light (indirect)
|
||||
indirect_light += ambient::ambient_light(in.world_position, in.N, in.V, NdotV, diffuse_color, F0, perceptual_roughness, diffuse_occlusion);
|
||||
// If we are lightmapped, disable the ambient contribution if requested.
|
||||
// This is to avoid double-counting ambient light. (It might be part of the lightmap)
|
||||
#ifdef LIGHTMAP
|
||||
let enable_ambient = view_bindings::lights.ambient_light_affects_lightmapped_meshes != 0u;
|
||||
#else // LIGHTMAP
|
||||
let enable_ambient = true;
|
||||
#endif // LIGHTMAP
|
||||
if (enable_ambient) {
|
||||
indirect_light += ambient::ambient_light(in.world_position, in.N, in.V, NdotV, diffuse_color, F0, perceptual_roughness, diffuse_occlusion);
|
||||
}
|
||||
|
||||
// we'll use the specular component of the transmitted environment
|
||||
// light in the call to `specular_transmissive_light()` below
|
||||
|
@ -46,11 +46,11 @@ use bevy_render::{
|
||||
render_graph::{RenderGraphExt, ViewNodeRunner},
|
||||
render_resource::SpecializedRenderPipelines,
|
||||
sync_component::SyncComponentPlugin,
|
||||
ExtractSchedule, Render, RenderApp, RenderSystems,
|
||||
ExtractSchedule, Render, RenderApp, RenderStartup, RenderSystems,
|
||||
};
|
||||
use render::{VolumetricFogNode, VolumetricFogPipeline, VolumetricFogUniformBuffer};
|
||||
|
||||
use crate::graph::NodePbr;
|
||||
use crate::{graph::NodePbr, volumetric_fog::render::init_volumetric_fog_pipeline};
|
||||
|
||||
pub mod render;
|
||||
|
||||
@ -84,6 +84,7 @@ impl Plugin for VolumetricFogPlugin {
|
||||
})
|
||||
.init_resource::<SpecializedRenderPipelines<VolumetricFogPipeline>>()
|
||||
.init_resource::<VolumetricFogUniformBuffer>()
|
||||
.add_systems(RenderStartup, init_volumetric_fog_pipeline)
|
||||
.add_systems(ExtractSchedule, render::extract_volumetric_fog)
|
||||
.add_systems(
|
||||
Render,
|
||||
@ -94,16 +95,7 @@ impl Plugin for VolumetricFogPlugin {
|
||||
.in_set(RenderSystems::Prepare)
|
||||
.before(prepare_core_3d_depth_textures),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
fn finish(&self, app: &mut App) {
|
||||
let Some(render_app) = app.get_sub_app_mut(RenderApp) else {
|
||||
return;
|
||||
};
|
||||
|
||||
render_app
|
||||
.init_resource::<VolumetricFogPipeline>()
|
||||
)
|
||||
.add_render_graph_node::<ViewNodeRunner<VolumetricFogNode>>(
|
||||
Core3d,
|
||||
NodePbr::VolumetricFog,
|
||||
|
@ -2,7 +2,7 @@
|
||||
|
||||
use core::array;
|
||||
|
||||
use bevy_asset::{load_embedded_asset, AssetId, Handle};
|
||||
use bevy_asset::{load_embedded_asset, AssetId, AssetServer, Handle};
|
||||
use bevy_color::ColorToComponents as _;
|
||||
use bevy_core_pipeline::{
|
||||
core_3d::Camera3d,
|
||||
@ -15,7 +15,7 @@ use bevy_ecs::{
|
||||
query::{Has, QueryItem, With},
|
||||
resource::Resource,
|
||||
system::{lifetimeless::Read, Commands, Local, Query, Res, ResMut},
|
||||
world::{FromWorld, World},
|
||||
world::World,
|
||||
};
|
||||
use bevy_image::{BevyDefault, Image};
|
||||
use bevy_math::{vec4, Mat3A, Mat4, Vec3, Vec3A, Vec4, Vec4Swizzles as _};
|
||||
@ -201,61 +201,61 @@ pub struct ViewFogVolume {
|
||||
#[derive(Resource, Default, Deref, DerefMut)]
|
||||
pub struct VolumetricFogUniformBuffer(pub DynamicUniformBuffer<VolumetricFogUniform>);
|
||||
|
||||
impl FromWorld for VolumetricFogPipeline {
|
||||
fn from_world(world: &mut World) -> Self {
|
||||
let render_device = world.resource::<RenderDevice>();
|
||||
let mesh_view_layouts = world.resource::<MeshPipelineViewLayouts>();
|
||||
pub fn init_volumetric_fog_pipeline(
|
||||
mut commands: Commands,
|
||||
render_device: Res<RenderDevice>,
|
||||
mesh_view_layouts: Res<MeshPipelineViewLayouts>,
|
||||
asset_server: Res<AssetServer>,
|
||||
) {
|
||||
// Create the bind group layout entries common to all bind group
|
||||
// layouts.
|
||||
let base_bind_group_layout_entries = &BindGroupLayoutEntries::single(
|
||||
ShaderStages::VERTEX_FRAGMENT,
|
||||
// `volumetric_fog`
|
||||
uniform_buffer::<VolumetricFogUniform>(true),
|
||||
);
|
||||
|
||||
// Create the bind group layout entries common to all bind group
|
||||
// layouts.
|
||||
let base_bind_group_layout_entries = &BindGroupLayoutEntries::single(
|
||||
ShaderStages::VERTEX_FRAGMENT,
|
||||
// `volumetric_fog`
|
||||
uniform_buffer::<VolumetricFogUniform>(true),
|
||||
);
|
||||
// For every combination of `VolumetricFogBindGroupLayoutKey` bits,
|
||||
// create a bind group layout.
|
||||
let bind_group_layouts = array::from_fn(|bits| {
|
||||
let flags = VolumetricFogBindGroupLayoutKey::from_bits_retain(bits as u8);
|
||||
|
||||
// For every combination of `VolumetricFogBindGroupLayoutKey` bits,
|
||||
// create a bind group layout.
|
||||
let bind_group_layouts = array::from_fn(|bits| {
|
||||
let flags = VolumetricFogBindGroupLayoutKey::from_bits_retain(bits as u8);
|
||||
let mut bind_group_layout_entries = base_bind_group_layout_entries.to_vec();
|
||||
|
||||
let mut bind_group_layout_entries = base_bind_group_layout_entries.to_vec();
|
||||
// `depth_texture`
|
||||
bind_group_layout_entries.extend_from_slice(&BindGroupLayoutEntries::with_indices(
|
||||
ShaderStages::FRAGMENT,
|
||||
((
|
||||
1,
|
||||
if flags.contains(VolumetricFogBindGroupLayoutKey::MULTISAMPLED) {
|
||||
texture_depth_2d_multisampled()
|
||||
} else {
|
||||
texture_depth_2d()
|
||||
},
|
||||
),),
|
||||
));
|
||||
|
||||
// `depth_texture`
|
||||
// `density_texture` and `density_sampler`
|
||||
if flags.contains(VolumetricFogBindGroupLayoutKey::DENSITY_TEXTURE) {
|
||||
bind_group_layout_entries.extend_from_slice(&BindGroupLayoutEntries::with_indices(
|
||||
ShaderStages::FRAGMENT,
|
||||
((
|
||||
1,
|
||||
if flags.contains(VolumetricFogBindGroupLayoutKey::MULTISAMPLED) {
|
||||
texture_depth_2d_multisampled()
|
||||
} else {
|
||||
texture_depth_2d()
|
||||
},
|
||||
),),
|
||||
(
|
||||
(2, texture_3d(TextureSampleType::Float { filterable: true })),
|
||||
(3, sampler(SamplerBindingType::Filtering)),
|
||||
),
|
||||
));
|
||||
|
||||
// `density_texture` and `density_sampler`
|
||||
if flags.contains(VolumetricFogBindGroupLayoutKey::DENSITY_TEXTURE) {
|
||||
bind_group_layout_entries.extend_from_slice(&BindGroupLayoutEntries::with_indices(
|
||||
ShaderStages::FRAGMENT,
|
||||
(
|
||||
(2, texture_3d(TextureSampleType::Float { filterable: true })),
|
||||
(3, sampler(SamplerBindingType::Filtering)),
|
||||
),
|
||||
));
|
||||
}
|
||||
|
||||
// Create the bind group layout.
|
||||
let description = flags.bind_group_layout_description();
|
||||
render_device.create_bind_group_layout(&*description, &bind_group_layout_entries)
|
||||
});
|
||||
|
||||
VolumetricFogPipeline {
|
||||
mesh_view_layouts: mesh_view_layouts.clone(),
|
||||
volumetric_view_bind_group_layouts: bind_group_layouts,
|
||||
shader: load_embedded_asset!(world, "volumetric_fog.wgsl"),
|
||||
}
|
||||
}
|
||||
|
||||
// Create the bind group layout.
|
||||
let description = flags.bind_group_layout_description();
|
||||
render_device.create_bind_group_layout(&*description, &bind_group_layout_entries)
|
||||
});
|
||||
|
||||
commands.insert_resource(VolumetricFogPipeline {
|
||||
mesh_view_layouts: mesh_view_layouts.clone(),
|
||||
volumetric_view_bind_group_layouts: bind_group_layouts,
|
||||
shader: load_embedded_asset!(asset_server.as_ref(), "volumetric_fog.wgsl"),
|
||||
});
|
||||
}
|
||||
|
||||
/// Extracts [`VolumetricFog`], [`FogVolume`], and [`VolumetricLight`]s
|
||||
|
@ -6,7 +6,7 @@ use crate::{
|
||||
use bevy_app::{App, Plugin, PostUpdate, Startup, Update};
|
||||
use bevy_asset::{
|
||||
embedded_asset, load_embedded_asset, prelude::AssetChanged, AsAssetId, Asset, AssetApp,
|
||||
AssetEventSystems, AssetId, Assets, Handle, UntypedAssetId,
|
||||
AssetEventSystems, AssetId, AssetServer, Assets, Handle, UntypedAssetId,
|
||||
};
|
||||
use bevy_color::{Color, ColorToComponents};
|
||||
use bevy_core_pipeline::core_3d::{
|
||||
@ -25,7 +25,6 @@ use bevy_platform::{
|
||||
hash::FixedHasher,
|
||||
};
|
||||
use bevy_reflect::{std_traits::ReflectDefault, Reflect};
|
||||
use bevy_render::camera::extract_cameras;
|
||||
use bevy_render::{
|
||||
batching::gpu_preprocessing::{GpuPreprocessingMode, GpuPreprocessingSupport},
|
||||
camera::ExtractedCamera,
|
||||
@ -54,6 +53,7 @@ use bevy_render::{
|
||||
},
|
||||
Extract, Render, RenderApp, RenderDebugFlags, RenderSystems,
|
||||
};
|
||||
use bevy_render::{camera::extract_cameras, RenderStartup};
|
||||
use core::{hash::Hash, ops::Range};
|
||||
use tracing::error;
|
||||
|
||||
@ -132,6 +132,7 @@ impl Plugin for WireframePlugin {
|
||||
Node3d::PostProcessing,
|
||||
),
|
||||
)
|
||||
.add_systems(RenderStartup, init_wireframe_3d_pipeline)
|
||||
.add_systems(
|
||||
ExtractSchedule,
|
||||
(
|
||||
@ -153,13 +154,6 @@ impl Plugin for WireframePlugin {
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
fn finish(&self, app: &mut App) {
|
||||
let Some(render_app) = app.get_sub_app_mut(RenderApp) else {
|
||||
return;
|
||||
};
|
||||
render_app.init_resource::<Wireframe3dPipeline>();
|
||||
}
|
||||
}
|
||||
|
||||
/// Enables wireframe rendering for any entity it is attached to.
|
||||
@ -331,13 +325,15 @@ pub struct Wireframe3dPipeline {
|
||||
shader: Handle<Shader>,
|
||||
}
|
||||
|
||||
impl FromWorld for Wireframe3dPipeline {
|
||||
fn from_world(render_world: &mut World) -> Self {
|
||||
Wireframe3dPipeline {
|
||||
mesh_pipeline: render_world.resource::<MeshPipeline>().clone(),
|
||||
shader: load_embedded_asset!(render_world, "render/wireframe.wgsl"),
|
||||
}
|
||||
}
|
||||
pub fn init_wireframe_3d_pipeline(
|
||||
mut commands: Commands,
|
||||
mesh_pipeline: Res<MeshPipeline>,
|
||||
asset_server: Res<AssetServer>,
|
||||
) {
|
||||
commands.insert_resource(Wireframe3dPipeline {
|
||||
mesh_pipeline: mesh_pipeline.clone(),
|
||||
shader: load_embedded_asset!(asset_server.as_ref(), "render/wireframe.wgsl"),
|
||||
});
|
||||
}
|
||||
|
||||
impl SpecializedMeshPipeline for Wireframe3dPipeline {
|
||||
|
@ -55,7 +55,7 @@ pub mod prelude {
|
||||
/// Note that systems reading these events in [`PreUpdate`](bevy_app::PreUpdate) will not report ordering
|
||||
/// ambiguities with picking backends. Take care to ensure such systems are explicitly ordered
|
||||
/// against [`PickingSystems::Backend`](crate::PickingSystems::Backend), or better, avoid reading `PointerHits` in `PreUpdate`.
|
||||
#[derive(Event, BufferedEvent, Debug, Clone, Reflect)]
|
||||
#[derive(BufferedEvent, Debug, Clone, Reflect)]
|
||||
#[reflect(Debug, Clone)]
|
||||
pub struct PointerHits {
|
||||
/// The pointer associated with this hit test.
|
||||
|
@ -48,7 +48,7 @@
|
||||
//! # use bevy_ecs::prelude::*;
|
||||
//! # use bevy_transform::prelude::*;
|
||||
//! # use bevy_picking::prelude::*;
|
||||
//! # #[derive(Event, BufferedEvent)]
|
||||
//! # #[derive(BufferedEvent)]
|
||||
//! # struct Greeting;
|
||||
//! fn setup(mut commands: Commands) {
|
||||
//! commands.spawn(Transform::default())
|
||||
|
@ -269,7 +269,7 @@ pub enum PointerAction {
|
||||
}
|
||||
|
||||
/// An input event effecting a pointer.
|
||||
#[derive(Event, BufferedEvent, Debug, Clone, Reflect)]
|
||||
#[derive(BufferedEvent, Debug, Clone, Reflect)]
|
||||
#[reflect(Clone)]
|
||||
pub struct PointerInput {
|
||||
/// The id of the pointer.
|
||||
|
@ -605,13 +605,11 @@ impl<'a> ReflectStruct<'a> {
|
||||
}
|
||||
|
||||
/// Get a collection of types which are exposed to the reflection API
|
||||
pub fn active_types(&self) -> Vec<Type> {
|
||||
// Collect via `IndexSet` to eliminate duplicate types.
|
||||
pub fn active_types(&self) -> IndexSet<Type> {
|
||||
// Collect into an `IndexSet` to eliminate duplicate types.
|
||||
self.active_fields()
|
||||
.map(|field| field.reflected_type().clone())
|
||||
.collect::<IndexSet<_>>()
|
||||
.into_iter()
|
||||
.collect::<Vec<_>>()
|
||||
}
|
||||
|
||||
/// Get an iterator of fields which are exposed to the reflection API.
|
||||
@ -634,7 +632,7 @@ impl<'a> ReflectStruct<'a> {
|
||||
}
|
||||
|
||||
pub fn where_clause_options(&self) -> WhereClauseOptions {
|
||||
WhereClauseOptions::new_with_fields(self.meta(), self.active_types().into_boxed_slice())
|
||||
WhereClauseOptions::new_with_types(self.meta(), self.active_types())
|
||||
}
|
||||
|
||||
/// Generates a `TokenStream` for `TypeInfo::Struct` or `TypeInfo::TupleStruct` construction.
|
||||
@ -841,13 +839,11 @@ impl<'a> ReflectEnum<'a> {
|
||||
}
|
||||
|
||||
/// Get a collection of types which are exposed to the reflection API
|
||||
pub fn active_types(&self) -> Vec<Type> {
|
||||
// Collect via `IndexSet` to eliminate duplicate types.
|
||||
pub fn active_types(&self) -> IndexSet<Type> {
|
||||
// Collect into an `IndexSet` to eliminate duplicate types.
|
||||
self.active_fields()
|
||||
.map(|field| field.reflected_type().clone())
|
||||
.collect::<IndexSet<_>>()
|
||||
.into_iter()
|
||||
.collect::<Vec<_>>()
|
||||
}
|
||||
|
||||
/// Get an iterator of fields which are exposed to the reflection API
|
||||
@ -856,7 +852,7 @@ impl<'a> ReflectEnum<'a> {
|
||||
}
|
||||
|
||||
pub fn where_clause_options(&self) -> WhereClauseOptions {
|
||||
WhereClauseOptions::new_with_fields(self.meta(), self.active_types().into_boxed_slice())
|
||||
WhereClauseOptions::new_with_types(self.meta(), self.active_types())
|
||||
}
|
||||
|
||||
/// Returns the `GetTypeRegistration` impl as a `TokenStream`.
|
||||
@ -869,7 +865,7 @@ impl<'a> ReflectEnum<'a> {
|
||||
crate::registration::impl_get_type_registration(
|
||||
where_clause_options,
|
||||
None,
|
||||
Some(self.active_fields().map(StructField::reflected_type)),
|
||||
Some(self.active_types().iter()),
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -13,23 +13,11 @@ pub(crate) fn impl_from_arg(where_clause_options: &WhereClauseOptions) -> proc_m
|
||||
quote! {
|
||||
impl #impl_generics #bevy_reflect::func::args::FromArg for #type_path #ty_generics #where_reflect_clause {
|
||||
type This<'from_arg> = #type_path #ty_generics;
|
||||
fn from_arg(arg: #bevy_reflect::func::args::Arg) -> #FQResult<Self::This<'_>, #bevy_reflect::func::args::ArgError> {
|
||||
fn from_arg(arg: #bevy_reflect::func::args::Arg) ->
|
||||
#FQResult<Self::This<'_>, #bevy_reflect::func::args::ArgError>
|
||||
{
|
||||
arg.take_owned()
|
||||
}
|
||||
}
|
||||
|
||||
impl #impl_generics #bevy_reflect::func::args::FromArg for &'static #type_path #ty_generics #where_reflect_clause {
|
||||
type This<'from_arg> = &'from_arg #type_path #ty_generics;
|
||||
fn from_arg(arg: #bevy_reflect::func::args::Arg) -> #FQResult<Self::This<'_>, #bevy_reflect::func::args::ArgError> {
|
||||
arg.take_ref()
|
||||
}
|
||||
}
|
||||
|
||||
impl #impl_generics #bevy_reflect::func::args::FromArg for &'static mut #type_path #ty_generics #where_reflect_clause {
|
||||
type This<'from_arg> = &'from_arg mut #type_path #ty_generics;
|
||||
fn from_arg(arg: #bevy_reflect::func::args::Arg) -> #FQResult<Self::This<'_>, #bevy_reflect::func::args::ArgError> {
|
||||
arg.take_mut()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -17,17 +17,5 @@ pub(crate) fn impl_get_ownership(
|
||||
#bevy_reflect::func::args::Ownership::Owned
|
||||
}
|
||||
}
|
||||
|
||||
impl #impl_generics #bevy_reflect::func::args::GetOwnership for &'_ #type_path #ty_generics #where_reflect_clause {
|
||||
fn ownership() -> #bevy_reflect::func::args::Ownership {
|
||||
#bevy_reflect::func::args::Ownership::Ref
|
||||
}
|
||||
}
|
||||
|
||||
impl #impl_generics #bevy_reflect::func::args::GetOwnership for &'_ mut #type_path #ty_generics #where_reflect_clause {
|
||||
fn ownership() -> #bevy_reflect::func::args::Ownership {
|
||||
#bevy_reflect::func::args::Ownership::Mut
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -13,21 +13,11 @@ pub(crate) fn impl_into_return(
|
||||
|
||||
quote! {
|
||||
impl #impl_generics #bevy_reflect::func::IntoReturn for #type_path #ty_generics #where_reflect_clause {
|
||||
fn into_return<'into_return>(self) -> #bevy_reflect::func::Return<'into_return> where Self: 'into_return {
|
||||
fn into_return<'into_return>(self) -> #bevy_reflect::func::Return<'into_return>
|
||||
where Self: 'into_return
|
||||
{
|
||||
#bevy_reflect::func::Return::Owned(#bevy_reflect::__macro_exports::alloc_utils::Box::new(self))
|
||||
}
|
||||
}
|
||||
|
||||
impl #impl_generics #bevy_reflect::func::IntoReturn for &#type_path #ty_generics #where_reflect_clause {
|
||||
fn into_return<'into_return>(self) -> #bevy_reflect::func::Return<'into_return> where Self: 'into_return {
|
||||
#bevy_reflect::func::Return::Ref(self)
|
||||
}
|
||||
}
|
||||
|
||||
impl #impl_generics #bevy_reflect::func::IntoReturn for &mut #type_path #ty_generics #where_reflect_clause {
|
||||
fn into_return<'into_return>(self) -> #bevy_reflect::func::Return<'into_return> where Self: 'into_return {
|
||||
#bevy_reflect::func::Return::Mut(self)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,5 +1,6 @@
|
||||
use crate::derive_data::ReflectMeta;
|
||||
use bevy_macro_utils::fq_std::{FQAny, FQSend, FQSync};
|
||||
use indexmap::IndexSet;
|
||||
use proc_macro2::{TokenStream, TokenTree};
|
||||
use quote::{quote, ToTokens};
|
||||
use syn::{punctuated::Punctuated, Ident, Token, Type, WhereClause};
|
||||
@ -7,22 +8,19 @@ use syn::{punctuated::Punctuated, Ident, Token, Type, WhereClause};
|
||||
/// Options defining how to extend the `where` clause for reflection.
|
||||
pub(crate) struct WhereClauseOptions<'a, 'b> {
|
||||
meta: &'a ReflectMeta<'b>,
|
||||
active_fields: Box<[Type]>,
|
||||
active_types: IndexSet<Type>,
|
||||
}
|
||||
|
||||
impl<'a, 'b> WhereClauseOptions<'a, 'b> {
|
||||
pub fn new(meta: &'a ReflectMeta<'b>) -> Self {
|
||||
Self {
|
||||
meta,
|
||||
active_fields: Box::new([]),
|
||||
active_types: IndexSet::new(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn new_with_fields(meta: &'a ReflectMeta<'b>, active_fields: Box<[Type]>) -> Self {
|
||||
Self {
|
||||
meta,
|
||||
active_fields,
|
||||
}
|
||||
pub fn new_with_types(meta: &'a ReflectMeta<'b>, active_types: IndexSet<Type>) -> Self {
|
||||
Self { meta, active_types }
|
||||
}
|
||||
|
||||
pub fn meta(&self) -> &'a ReflectMeta<'b> {
|
||||
@ -207,7 +205,7 @@ impl<'a, 'b> WhereClauseOptions<'a, 'b> {
|
||||
false
|
||||
}
|
||||
|
||||
Some(self.active_fields.iter().filter_map(move |ty| {
|
||||
Some(self.active_types.iter().filter_map(move |ty| {
|
||||
// Field type bounds are only required if `ty` is generic. How to determine that?
|
||||
// Search `ty`s token stream for identifiers that match the identifiers from the
|
||||
// function's type params. E.g. if `T` and `U` are the type param identifiers and
|
||||
|
@ -1,14 +1,17 @@
|
||||
use crate::func::args::{Arg, ArgError};
|
||||
use crate::{Reflect, TypePath};
|
||||
|
||||
/// A trait for types that can be created from an [`Arg`].
|
||||
///
|
||||
/// This trait exists so that types can be automatically converted into an [`Arg`]
|
||||
/// so they can be put into an [`ArgList`] and passed to a [`DynamicFunction`] or [`DynamicFunctionMut`].
|
||||
/// so they can be put into an [`ArgList`] and passed to a [`DynamicFunction`] or
|
||||
/// [`DynamicFunctionMut`].
|
||||
///
|
||||
/// This trait is used instead of a blanket [`From`] implementation due to coherence issues:
|
||||
/// we can't implement `From<T>` for both `T` and `&T`/`&mut T`.
|
||||
///
|
||||
/// This trait is automatically implemented when using the `Reflect` [derive macro].
|
||||
/// This trait is automatically implemented for non-reference types when using the `Reflect`
|
||||
/// [derive macro]. Blanket impls cover `&T` and `&mut T`.
|
||||
///
|
||||
/// [`ArgList`]: crate::func::args::ArgList
|
||||
/// [`DynamicFunction`]: crate::func::DynamicFunction
|
||||
@ -29,6 +32,22 @@ pub trait FromArg {
|
||||
fn from_arg(arg: Arg) -> Result<Self::This<'_>, ArgError>;
|
||||
}
|
||||
|
||||
// Blanket impl.
|
||||
impl<T: Reflect + TypePath> FromArg for &'static T {
|
||||
type This<'a> = &'a T;
|
||||
fn from_arg(arg: Arg) -> Result<Self::This<'_>, ArgError> {
|
||||
arg.take_ref()
|
||||
}
|
||||
}
|
||||
|
||||
// Blanket impl.
|
||||
impl<T: Reflect + TypePath> FromArg for &'static mut T {
|
||||
type This<'a> = &'a mut T;
|
||||
fn from_arg(arg: Arg) -> Result<Self::This<'_>, ArgError> {
|
||||
arg.take_mut()
|
||||
}
|
||||
}
|
||||
|
||||
/// Implements the [`FromArg`] trait for the given type.
|
||||
///
|
||||
/// This will implement it for `$ty`, `&$ty`, and `&mut $ty`.
|
||||
@ -40,18 +59,13 @@ macro_rules! impl_from_arg {
|
||||
(
|
||||
$ty: ty
|
||||
$(;
|
||||
<
|
||||
$($T: ident $(: $T1: tt $(+ $T2: tt)*)?),*
|
||||
>
|
||||
< $($T: ident $(: $T1: tt $(+ $T2: tt)*)?),* >
|
||||
)?
|
||||
$(
|
||||
[
|
||||
$(const $N: ident : $size: ident),*
|
||||
]
|
||||
[ $(const $N: ident : $size: ident),* ]
|
||||
)?
|
||||
$(
|
||||
where
|
||||
$($U: ty $(: $U1: tt $(+ $U2: tt)*)?),*
|
||||
where $($U: ty $(: $U1: tt $(+ $U2: tt)*)?),*
|
||||
)?
|
||||
) => {
|
||||
impl <
|
||||
@ -59,45 +73,16 @@ macro_rules! impl_from_arg {
|
||||
$(, $(const $N : $size),*)?
|
||||
> $crate::func::args::FromArg for $ty
|
||||
$(
|
||||
where
|
||||
$($U $(: $U1 $(+ $U2)*)?),*
|
||||
where $($U $(: $U1 $(+ $U2)*)?),*
|
||||
)?
|
||||
{
|
||||
type This<'from_arg> = $ty;
|
||||
fn from_arg(arg: $crate::func::args::Arg) -> Result<Self::This<'_>, $crate::func::args::ArgError> {
|
||||
fn from_arg(arg: $crate::func::args::Arg) ->
|
||||
Result<Self::This<'_>, $crate::func::args::ArgError>
|
||||
{
|
||||
arg.take_owned()
|
||||
}
|
||||
}
|
||||
|
||||
impl <
|
||||
$($($T $(: $T1 $(+ $T2)*)?),*)?
|
||||
$(, $(const $N : $size),*)?
|
||||
> $crate::func::args::FromArg for &'static $ty
|
||||
$(
|
||||
where
|
||||
$($U $(: $U1 $(+ $U2)*)?),*
|
||||
)?
|
||||
{
|
||||
type This<'from_arg> = &'from_arg $ty;
|
||||
fn from_arg(arg: $crate::func::args::Arg) -> Result<Self::This<'_>, $crate::func::args::ArgError> {
|
||||
arg.take_ref()
|
||||
}
|
||||
}
|
||||
|
||||
impl <
|
||||
$($($T $(: $T1 $(+ $T2)*)?),*)?
|
||||
$(, $(const $N : $size),*)?
|
||||
> $crate::func::args::FromArg for &'static mut $ty
|
||||
$(
|
||||
where
|
||||
$($U $(: $U1 $(+ $U2)*)?),*
|
||||
)?
|
||||
{
|
||||
type This<'from_arg> = &'from_arg mut $ty;
|
||||
fn from_arg(arg: $crate::func::args::Arg) -> Result<Self::This<'_>, $crate::func::args::ArgError> {
|
||||
arg.take_mut()
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -1,21 +1,5 @@
|
||||
use core::fmt::{Display, Formatter};
|
||||
|
||||
/// A trait for getting the ownership of a type.
|
||||
///
|
||||
/// This trait exists so that [`TypedFunction`] can automatically generate
|
||||
/// [`FunctionInfo`] containing the proper [`Ownership`] for its [argument] types.
|
||||
///
|
||||
/// This trait is automatically implemented when using the `Reflect` [derive macro].
|
||||
///
|
||||
/// [`TypedFunction`]: crate::func::TypedFunction
|
||||
/// [`FunctionInfo`]: crate::func::FunctionInfo
|
||||
/// [argument]: crate::func::args::Arg
|
||||
/// [derive macro]: derive@crate::Reflect
|
||||
pub trait GetOwnership {
|
||||
/// Returns the ownership of [`Self`].
|
||||
fn ownership() -> Ownership;
|
||||
}
|
||||
|
||||
/// The ownership of a type.
|
||||
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
|
||||
pub enum Ownership {
|
||||
@ -37,6 +21,39 @@ impl Display for Ownership {
|
||||
}
|
||||
}
|
||||
|
||||
/// A trait for getting the ownership of a type.
|
||||
///
|
||||
/// This trait exists so that [`TypedFunction`] can automatically generate
|
||||
/// [`FunctionInfo`] containing the proper [`Ownership`] for its [argument] types.
|
||||
///
|
||||
/// This trait is automatically implemented for non-reference types when using the `Reflect`
|
||||
/// [derive macro]. Blanket impls cover `&T` and `&mut T`.
|
||||
///
|
||||
/// [`TypedFunction`]: crate::func::TypedFunction
|
||||
/// [`FunctionInfo`]: crate::func::FunctionInfo
|
||||
/// [argument]: crate::func::args::Arg
|
||||
/// [derive macro]: derive@crate::Reflect
|
||||
pub trait GetOwnership {
|
||||
/// Returns the ownership of [`Self`].
|
||||
fn ownership() -> Ownership {
|
||||
Ownership::Owned
|
||||
}
|
||||
}
|
||||
|
||||
// Blanket impl.
|
||||
impl<T> GetOwnership for &'_ T {
|
||||
fn ownership() -> Ownership {
|
||||
Ownership::Ref
|
||||
}
|
||||
}
|
||||
|
||||
// Blanket impl.
|
||||
impl<T> GetOwnership for &'_ mut T {
|
||||
fn ownership() -> Ownership {
|
||||
Ownership::Mut
|
||||
}
|
||||
}
|
||||
|
||||
/// Implements the [`GetOwnership`] trait for the given type.
|
||||
///
|
||||
/// This will implement it for `$ty`, `&$ty`, and `&mut $ty`.
|
||||
@ -48,18 +65,13 @@ macro_rules! impl_get_ownership {
|
||||
(
|
||||
$ty: ty
|
||||
$(;
|
||||
<
|
||||
$($T: ident $(: $T1: tt $(+ $T2: tt)*)?),*
|
||||
>
|
||||
< $($T: ident $(: $T1: tt $(+ $T2: tt)*)?),* >
|
||||
)?
|
||||
$(
|
||||
[
|
||||
$(const $N: ident : $size: ident),*
|
||||
]
|
||||
[ $(const $N: ident : $size: ident),* ]
|
||||
)?
|
||||
$(
|
||||
where
|
||||
$($U: ty $(: $U1: tt $(+ $U2: tt)*)?),*
|
||||
where $($U: ty $(: $U1: tt $(+ $U2: tt)*)?),*
|
||||
)?
|
||||
) => {
|
||||
impl <
|
||||
@ -67,42 +79,9 @@ macro_rules! impl_get_ownership {
|
||||
$(, $(const $N : $size),*)?
|
||||
> $crate::func::args::GetOwnership for $ty
|
||||
$(
|
||||
where
|
||||
$($U $(: $U1 $(+ $U2)*)?),*
|
||||
where $($U $(: $U1 $(+ $U2)*)?),*
|
||||
)?
|
||||
{
|
||||
fn ownership() -> $crate::func::args::Ownership {
|
||||
$crate::func::args::Ownership::Owned
|
||||
}
|
||||
}
|
||||
|
||||
impl <
|
||||
$($($T $(: $T1 $(+ $T2)*)?),*)?
|
||||
$(, $(const $N : $size),*)?
|
||||
> $crate::func::args::GetOwnership for &'_ $ty
|
||||
$(
|
||||
where
|
||||
$($U $(: $U1 $(+ $U2)*)?),*
|
||||
)?
|
||||
{
|
||||
fn ownership() -> $crate::func::args::Ownership {
|
||||
$crate::func::args::Ownership::Ref
|
||||
}
|
||||
}
|
||||
|
||||
impl <
|
||||
$($($T $(: $T1 $(+ $T2)*)?),*)?
|
||||
$(, $(const $N : $size),*)?
|
||||
> $crate::func::args::GetOwnership for &'_ mut $ty
|
||||
$(
|
||||
where
|
||||
$($U $(: $U1 $(+ $U2)*)?),*
|
||||
)?
|
||||
{
|
||||
fn ownership() -> $crate::func::args::Ownership {
|
||||
$crate::func::args::Ownership::Mut
|
||||
}
|
||||
}
|
||||
{}
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -28,69 +28,49 @@ macro_rules! impl_function_traits {
|
||||
(
|
||||
$ty: ty
|
||||
$(;
|
||||
<
|
||||
$($T: ident $(: $T1: tt $(+ $T2: tt)*)?),*
|
||||
>
|
||||
< $($T: ident $(: $T1: tt $(+ $T2: tt)*)?),* >
|
||||
)?
|
||||
$(
|
||||
[
|
||||
$(const $N: ident : $size: ident),*
|
||||
]
|
||||
[ $(const $N: ident : $size: ident),* ]
|
||||
)?
|
||||
$(
|
||||
where
|
||||
$($U: ty $(: $U1: tt $(+ $U2: tt)*)?),*
|
||||
where $($U: ty $(: $U1: tt $(+ $U2: tt)*)?),*
|
||||
)?
|
||||
) => {
|
||||
$crate::func::args::impl_get_ownership!(
|
||||
$ty
|
||||
$(;
|
||||
<
|
||||
$($T $(: $T1 $(+ $T2)*)?),*
|
||||
>
|
||||
< $($T $(: $T1 $(+ $T2)*)?),* >
|
||||
)?
|
||||
$(
|
||||
[
|
||||
$(const $N : $size),*
|
||||
]
|
||||
[ $(const $N : $size),* ]
|
||||
)?
|
||||
$(
|
||||
where
|
||||
$($U $(: $U1 $(+ $U2)*)?),*
|
||||
where $($U $(: $U1 $(+ $U2)*)?),*
|
||||
)?
|
||||
);
|
||||
$crate::func::args::impl_from_arg!(
|
||||
$ty
|
||||
$(;
|
||||
<
|
||||
$($T $(: $T1 $(+ $T2)*)?),*
|
||||
>
|
||||
< $($T $(: $T1 $(+ $T2)*)?),* >
|
||||
)?
|
||||
$(
|
||||
[
|
||||
$(const $N : $size),*
|
||||
]
|
||||
[ $(const $N : $size),* ]
|
||||
)?
|
||||
$(
|
||||
where
|
||||
$($U $(: $U1 $(+ $U2)*)?),*
|
||||
where $($U $(: $U1 $(+ $U2)*)?),*
|
||||
)?
|
||||
);
|
||||
$crate::func::impl_into_return!(
|
||||
$ty
|
||||
$(;
|
||||
<
|
||||
$($T $(: $T1 $(+ $T2)*)?),*
|
||||
>
|
||||
< $($T $(: $T1 $(+ $T2)*)?),* >
|
||||
)?
|
||||
$(
|
||||
[
|
||||
$(const $N : $size),*
|
||||
]
|
||||
[ $(const $N : $size),* ]
|
||||
)?
|
||||
$(
|
||||
where
|
||||
$($U $(: $U1 $(+ $U2)*)?),*
|
||||
where $($U $(: $U1 $(+ $U2)*)?),*
|
||||
)?
|
||||
);
|
||||
};
|
||||
|
@ -76,7 +76,8 @@ impl<'a> Return<'a> {
|
||||
/// This trait is used instead of a blanket [`Into`] implementation due to coherence issues:
|
||||
/// we can't implement `Into<Return>` for both `T` and `&T`/`&mut T`.
|
||||
///
|
||||
/// This trait is automatically implemented when using the `Reflect` [derive macro].
|
||||
/// This trait is automatically implemented for non-reference types when using the `Reflect`
|
||||
/// [derive macro]. Blanket impls cover `&T` and `&mut T`.
|
||||
///
|
||||
/// [`ReflectFn`]: crate::func::ReflectFn
|
||||
/// [`ReflectFnMut`]: crate::func::ReflectFnMut
|
||||
@ -88,6 +89,26 @@ pub trait IntoReturn {
|
||||
Self: 'a;
|
||||
}
|
||||
|
||||
// Blanket impl.
|
||||
impl<T: PartialReflect> IntoReturn for &'_ T {
|
||||
fn into_return<'a>(self) -> Return<'a>
|
||||
where
|
||||
Self: 'a,
|
||||
{
|
||||
Return::Ref(self)
|
||||
}
|
||||
}
|
||||
|
||||
// Blanket impl.
|
||||
impl<T: PartialReflect> IntoReturn for &'_ mut T {
|
||||
fn into_return<'a>(self) -> Return<'a>
|
||||
where
|
||||
Self: 'a,
|
||||
{
|
||||
Return::Mut(self)
|
||||
}
|
||||
}
|
||||
|
||||
impl IntoReturn for () {
|
||||
fn into_return<'a>(self) -> Return<'a> {
|
||||
Return::unit()
|
||||
@ -105,18 +126,13 @@ macro_rules! impl_into_return {
|
||||
(
|
||||
$ty: ty
|
||||
$(;
|
||||
<
|
||||
$($T: ident $(: $T1: tt $(+ $T2: tt)*)?),*
|
||||
>
|
||||
< $($T: ident $(: $T1: tt $(+ $T2: tt)*)?),* >
|
||||
)?
|
||||
$(
|
||||
[
|
||||
$(const $N: ident : $size: ident),*
|
||||
]
|
||||
[ $(const $N: ident : $size: ident),* ]
|
||||
)?
|
||||
$(
|
||||
where
|
||||
$($U: ty $(: $U1: tt $(+ $U2: tt)*)?),*
|
||||
where $($U: ty $(: $U1: tt $(+ $U2: tt)*)?),*
|
||||
)?
|
||||
) => {
|
||||
impl <
|
||||
@ -124,42 +140,15 @@ macro_rules! impl_into_return {
|
||||
$(, $(const $N : $size),*)?
|
||||
> $crate::func::IntoReturn for $ty
|
||||
$(
|
||||
where
|
||||
$($U $(: $U1 $(+ $U2)*)?),*
|
||||
where $($U $(: $U1 $(+ $U2)*)?),*
|
||||
)?
|
||||
{
|
||||
fn into_return<'into_return>(self) -> $crate::func::Return<'into_return> where Self: 'into_return {
|
||||
fn into_return<'into_return>(self) -> $crate::func::Return<'into_return>
|
||||
where Self: 'into_return
|
||||
{
|
||||
$crate::func::Return::Owned(bevy_platform::prelude::Box::new(self))
|
||||
}
|
||||
}
|
||||
|
||||
impl <
|
||||
$($($T $(: $T1 $(+ $T2)*)?),*)?
|
||||
$(, $(const $N : $size),*)?
|
||||
> $crate::func::IntoReturn for &'static $ty
|
||||
$(
|
||||
where
|
||||
$($U $(: $U1 $(+ $U2)*)?),*
|
||||
)?
|
||||
{
|
||||
fn into_return<'into_return>(self) -> $crate::func::Return<'into_return> where Self: 'into_return {
|
||||
$crate::func::Return::Ref(self)
|
||||
}
|
||||
}
|
||||
|
||||
impl <
|
||||
$($($T $(: $T1 $(+ $T2)*)?),*)?
|
||||
$(, $(const $N : $size),*)?
|
||||
> $crate::func::IntoReturn for &'static mut $ty
|
||||
$(
|
||||
where
|
||||
$($U $(: $U1 $(+ $U2)*)?),*
|
||||
)?
|
||||
{
|
||||
fn into_return<'into_return>(self) -> $crate::func::Return<'into_return> where Self: 'into_return {
|
||||
$crate::func::Return::Mut(self)
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -153,6 +153,3 @@ impl FromReflect for &'static Location<'static> {
|
||||
reflect.try_downcast_ref::<Self>().copied()
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "functions")]
|
||||
crate::func::macros::impl_function_traits!(&'static Location<'static>);
|
||||
|
@ -293,9 +293,6 @@ impl FromReflect for &'static str {
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "functions")]
|
||||
crate::func::macros::impl_function_traits!(&'static str);
|
||||
|
||||
impl<T: Reflect + MaybeTyped + TypePath + GetTypeRegistration, const N: usize> Array for [T; N] {
|
||||
#[inline]
|
||||
fn get(&self, index: usize) -> Option<&dyn PartialReflect> {
|
||||
|
@ -160,9 +160,6 @@ impl FromReflect for &'static Path {
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "functions")]
|
||||
crate::func::macros::impl_function_traits!(&'static Path);
|
||||
|
||||
impl PartialReflect for Cow<'static, Path> {
|
||||
fn get_represented_type_info(&self) -> Option<&'static TypeInfo> {
|
||||
Some(<Self as Typed>::type_info())
|
||||
|
@ -433,6 +433,17 @@ pub fn extract_cameras(
|
||||
mapper: Extract<Query<&RenderEntity>>,
|
||||
) {
|
||||
let primary_window = primary_window.iter().next();
|
||||
type ExtractedCameraComponents = (
|
||||
ExtractedCamera,
|
||||
ExtractedView,
|
||||
RenderVisibleEntities,
|
||||
TemporalJitter,
|
||||
MipBias,
|
||||
RenderLayers,
|
||||
Projection,
|
||||
NoIndirectDrawing,
|
||||
ViewUniformOffset,
|
||||
);
|
||||
for (
|
||||
main_entity,
|
||||
render_entity,
|
||||
@ -452,17 +463,9 @@ pub fn extract_cameras(
|
||||
) in query.iter()
|
||||
{
|
||||
if !camera.is_active {
|
||||
commands.entity(render_entity).remove::<(
|
||||
ExtractedCamera,
|
||||
ExtractedView,
|
||||
RenderVisibleEntities,
|
||||
TemporalJitter,
|
||||
MipBias,
|
||||
RenderLayers,
|
||||
Projection,
|
||||
NoIndirectDrawing,
|
||||
ViewUniformOffset,
|
||||
)>();
|
||||
commands
|
||||
.entity(render_entity)
|
||||
.remove::<ExtractedCameraComponents>();
|
||||
continue;
|
||||
}
|
||||
|
||||
@ -481,6 +484,9 @@ pub fn extract_cameras(
|
||||
camera.physical_target_size(),
|
||||
) {
|
||||
if target_size.x == 0 || target_size.y == 0 {
|
||||
commands
|
||||
.entity(render_entity)
|
||||
.remove::<ExtractedCameraComponents>();
|
||||
continue;
|
||||
}
|
||||
|
||||
|
@ -77,7 +77,10 @@ impl Plugin for GpuReadbackPlugin {
|
||||
#[derive(Component, ExtractComponent, Clone, Debug)]
|
||||
pub enum Readback {
|
||||
Texture(Handle<Image>),
|
||||
Buffer(Handle<ShaderStorageBuffer>),
|
||||
Buffer {
|
||||
buffer: Handle<ShaderStorageBuffer>,
|
||||
start_offset_and_size: Option<(u64, u64)>,
|
||||
},
|
||||
}
|
||||
|
||||
impl Readback {
|
||||
@ -86,9 +89,21 @@ impl Readback {
|
||||
Self::Texture(image)
|
||||
}
|
||||
|
||||
/// Create a readback component for a buffer using the given handle.
|
||||
/// Create a readback component for a full buffer using the given handle.
|
||||
pub fn buffer(buffer: Handle<ShaderStorageBuffer>) -> Self {
|
||||
Self::Buffer(buffer)
|
||||
Self::Buffer {
|
||||
buffer,
|
||||
start_offset_and_size: None,
|
||||
}
|
||||
}
|
||||
|
||||
/// Create a readback component for a buffer range using the given handle, a start offset in bytes
|
||||
/// and a number of bytes to read.
|
||||
pub fn buffer_range(buffer: Handle<ShaderStorageBuffer>, start_offset: u64, size: u64) -> Self {
|
||||
Self::Buffer {
|
||||
buffer,
|
||||
start_offset_and_size: Some((start_offset, size)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -193,9 +208,8 @@ enum ReadbackSource {
|
||||
size: Extent3d,
|
||||
},
|
||||
Buffer {
|
||||
src_start: u64,
|
||||
dst_start: u64,
|
||||
buffer: Buffer,
|
||||
start_offset_and_size: Option<(u64, u64)>,
|
||||
},
|
||||
}
|
||||
|
||||
@ -266,16 +280,30 @@ fn prepare_buffers(
|
||||
});
|
||||
}
|
||||
}
|
||||
Readback::Buffer(buffer) => {
|
||||
Readback::Buffer {
|
||||
buffer,
|
||||
start_offset_and_size,
|
||||
} => {
|
||||
if let Some(ssbo) = ssbos.get(buffer) {
|
||||
let size = ssbo.buffer.size();
|
||||
let full_size = ssbo.buffer.size();
|
||||
let size = start_offset_and_size
|
||||
.map(|(start, size)| {
|
||||
let end = start + size;
|
||||
if end > full_size {
|
||||
panic!(
|
||||
"Tried to read past the end of the buffer (start: {start}, \
|
||||
size: {size}, buffer size: {full_size})."
|
||||
);
|
||||
}
|
||||
size
|
||||
})
|
||||
.unwrap_or(full_size);
|
||||
let buffer = buffer_pool.get(&render_device, size);
|
||||
let (tx, rx) = async_channel::bounded(1);
|
||||
readbacks.requested.push(GpuReadback {
|
||||
entity: entity.id(),
|
||||
src: ReadbackSource::Buffer {
|
||||
src_start: 0,
|
||||
dst_start: 0,
|
||||
start_offset_and_size: *start_offset_and_size,
|
||||
buffer: ssbo.buffer.clone(),
|
||||
},
|
||||
buffer,
|
||||
@ -307,17 +335,11 @@ pub(crate) fn submit_readback_commands(world: &World, command_encoder: &mut Comm
|
||||
);
|
||||
}
|
||||
ReadbackSource::Buffer {
|
||||
src_start,
|
||||
dst_start,
|
||||
buffer,
|
||||
start_offset_and_size,
|
||||
} => {
|
||||
command_encoder.copy_buffer_to_buffer(
|
||||
buffer,
|
||||
*src_start,
|
||||
&readback.buffer,
|
||||
*dst_start,
|
||||
buffer.size(),
|
||||
);
|
||||
let (src_start, size) = start_offset_and_size.unwrap_or((0, buffer.size()));
|
||||
command_encoder.copy_buffer_to_buffer(buffer, src_start, &readback.buffer, 0, size);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -4,7 +4,7 @@
|
||||
//!
|
||||
//! See [`SolariPlugins`] for more info.
|
||||
//!
|
||||
//! 
|
||||
//! 
|
||||
pub mod pathtracer;
|
||||
pub mod realtime;
|
||||
pub mod scene;
|
||||
|
@ -1,7 +1,7 @@
|
||||
use core::{marker::PhantomData, mem};
|
||||
|
||||
use bevy_ecs::{
|
||||
event::{BufferedEvent, Event, EventReader, EventWriter},
|
||||
event::{BufferedEvent, EventReader, EventWriter},
|
||||
schedule::{IntoScheduleConfigs, Schedule, ScheduleLabel, Schedules, SystemSet},
|
||||
system::{Commands, In, ResMut},
|
||||
world::World,
|
||||
@ -61,7 +61,7 @@ pub struct StateTransition;
|
||||
/// This includes identity transitions, where `exited` and `entered` have the same value.
|
||||
///
|
||||
/// If you know exactly what state you want to respond to ahead of time, consider [`OnEnter`], [`OnTransition`], or [`OnExit`]
|
||||
#[derive(Debug, Copy, Clone, PartialEq, Eq, Event, BufferedEvent)]
|
||||
#[derive(Debug, Copy, Clone, PartialEq, Eq, BufferedEvent)]
|
||||
pub struct StateTransitionEvent<S: States> {
|
||||
/// The state being exited.
|
||||
pub exited: Option<S>,
|
||||
|
@ -187,7 +187,7 @@ impl StateScopedEventsAppExt for SubApp {
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::app::StatesPlugin;
|
||||
use bevy_ecs::event::{BufferedEvent, Event};
|
||||
use bevy_ecs::event::BufferedEvent;
|
||||
use bevy_state::prelude::*;
|
||||
|
||||
#[derive(States, Default, Clone, Hash, Eq, PartialEq, Debug)]
|
||||
@ -197,10 +197,10 @@ mod tests {
|
||||
B,
|
||||
}
|
||||
|
||||
#[derive(Event, BufferedEvent, Debug)]
|
||||
#[derive(BufferedEvent, Debug)]
|
||||
struct StandardEvent;
|
||||
|
||||
#[derive(Event, BufferedEvent, Debug)]
|
||||
#[derive(BufferedEvent, Debug)]
|
||||
struct StateScopedEvent;
|
||||
|
||||
#[test]
|
||||
|
@ -186,8 +186,7 @@ mod tests {
|
||||
use bevy_app::{App, FixedUpdate, Startup, Update};
|
||||
use bevy_ecs::{
|
||||
event::{
|
||||
BufferedEvent, Event, EventReader, EventRegistry, EventWriter, Events,
|
||||
ShouldUpdateEvents,
|
||||
BufferedEvent, EventReader, EventRegistry, EventWriter, Events, ShouldUpdateEvents,
|
||||
},
|
||||
resource::Resource,
|
||||
system::{Local, Res, ResMut},
|
||||
@ -196,7 +195,7 @@ mod tests {
|
||||
use core::time::Duration;
|
||||
use std::println;
|
||||
|
||||
#[derive(Event, BufferedEvent)]
|
||||
#[derive(BufferedEvent)]
|
||||
struct TestEvent<T: Default> {
|
||||
sender: std::sync::mpsc::Sender<T>,
|
||||
}
|
||||
@ -209,7 +208,7 @@ mod tests {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Event, BufferedEvent)]
|
||||
#[derive(BufferedEvent)]
|
||||
struct DummyEvent;
|
||||
|
||||
#[derive(Resource, Default)]
|
||||
|
@ -119,7 +119,7 @@ use {
|
||||
/// # use bevy_ecs::prelude::*;
|
||||
/// # use bevy_time::prelude::*;
|
||||
/// #
|
||||
/// #[derive(Event, BufferedEvent)]
|
||||
/// #[derive(BufferedEvent)]
|
||||
/// struct PauseEvent(bool);
|
||||
///
|
||||
/// fn pause_system(mut time: ResMut<Time<Virtual>>, mut events: EventReader<PauseEvent>) {
|
||||
|
@ -5,13 +5,25 @@ use core::time::Duration;
|
||||
|
||||
/// Tracks elapsed time. Enters the finished state once `duration` is reached.
|
||||
///
|
||||
/// Non repeating timers will stop tracking and stay in the finished state until reset.
|
||||
/// Repeating timers will only be in the finished state on each tick `duration` is reached or
|
||||
/// exceeded, and can still be reset at any given point.
|
||||
///
|
||||
/// Paused timers will not have elapsed time increased.
|
||||
///
|
||||
/// Note that in order to advance the timer [`tick`](Timer::tick) **MUST** be called.
|
||||
///
|
||||
/// # Timer modes
|
||||
///
|
||||
/// There are two timer modes ([`TimerMode`]):
|
||||
///
|
||||
/// - Non repeating timers will stop tracking and stay in the finished state until reset.
|
||||
/// - Repeating timers will only be in the finished state on each tick `duration` is reached or
|
||||
/// exceeded, and can still be reset at any given point.
|
||||
///
|
||||
/// # Pausing timers
|
||||
///
|
||||
/// You can pause a timer using [`Timer::pause`]. Paused timers will not have elapsed time increased.
|
||||
///
|
||||
/// # Elapsing multiple times a frame
|
||||
///
|
||||
/// Repeating timers might elapse multiple times per frame if the time is advanced by more than the timer duration.
|
||||
/// You can check how many times a timer elapsed each tick with [`Timer::times_finished_this_tick`].
|
||||
/// For non-repeating timers, this will always be 0 or 1.
|
||||
#[derive(Clone, Debug, Default, PartialEq, Eq)]
|
||||
#[cfg_attr(feature = "serialize", derive(serde::Deserialize, serde::Serialize))]
|
||||
#[cfg_attr(
|
||||
|
@ -63,7 +63,7 @@ impl ColorStop {
|
||||
}
|
||||
|
||||
// Set the interpolation midpoint between this and the following stop
|
||||
pub fn with_hint(mut self, hint: f32) -> Self {
|
||||
pub const fn with_hint(mut self, hint: f32) -> Self {
|
||||
self.hint = hint;
|
||||
self
|
||||
}
|
||||
@ -175,7 +175,7 @@ impl AngularColorStop {
|
||||
}
|
||||
|
||||
// Set the interpolation midpoint between this and the following stop
|
||||
pub fn with_hint(mut self, hint: f32) -> Self {
|
||||
pub const fn with_hint(mut self, hint: f32) -> Self {
|
||||
self.hint = hint;
|
||||
self
|
||||
}
|
||||
@ -387,7 +387,7 @@ impl RadialGradient {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn in_color_space(mut self, color_space: InterpolationColorSpace) -> Self {
|
||||
pub const fn in_color_space(mut self, color_space: InterpolationColorSpace) -> Self {
|
||||
self.color_space = color_space;
|
||||
self
|
||||
}
|
||||
@ -437,18 +437,18 @@ impl ConicGradient {
|
||||
}
|
||||
|
||||
/// Sets the starting angle of the gradient in radians
|
||||
pub fn with_start(mut self, start: f32) -> Self {
|
||||
pub const fn with_start(mut self, start: f32) -> Self {
|
||||
self.start = start;
|
||||
self
|
||||
}
|
||||
|
||||
/// Sets the position of the gradient
|
||||
pub fn with_position(mut self, position: UiPosition) -> Self {
|
||||
pub const fn with_position(mut self, position: UiPosition) -> Self {
|
||||
self.position = position;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn in_color_space(mut self, color_space: InterpolationColorSpace) -> Self {
|
||||
pub const fn in_color_space(mut self, color_space: InterpolationColorSpace) -> Self {
|
||||
self.color_space = color_space;
|
||||
self
|
||||
}
|
||||
@ -478,7 +478,7 @@ pub enum Gradient {
|
||||
|
||||
impl Gradient {
|
||||
/// Returns true if the gradient has no stops.
|
||||
pub fn is_empty(&self) -> bool {
|
||||
pub const fn is_empty(&self) -> bool {
|
||||
match self {
|
||||
Gradient::Linear(gradient) => gradient.stops.is_empty(),
|
||||
Gradient::Radial(gradient) => gradient.stops.is_empty(),
|
||||
@ -578,19 +578,19 @@ pub enum RadialGradientShape {
|
||||
Ellipse(Val, Val),
|
||||
}
|
||||
|
||||
fn close_side(p: f32, h: f32) -> f32 {
|
||||
const fn close_side(p: f32, h: f32) -> f32 {
|
||||
(-h - p).abs().min((h - p).abs())
|
||||
}
|
||||
|
||||
fn far_side(p: f32, h: f32) -> f32 {
|
||||
const fn far_side(p: f32, h: f32) -> f32 {
|
||||
(-h - p).abs().max((h - p).abs())
|
||||
}
|
||||
|
||||
fn close_side2(p: Vec2, h: Vec2) -> f32 {
|
||||
const fn close_side2(p: Vec2, h: Vec2) -> f32 {
|
||||
close_side(p.x, h.x).min(close_side(p.y, h.y))
|
||||
}
|
||||
|
||||
fn far_side2(p: Vec2, h: Vec2) -> f32 {
|
||||
const fn far_side2(p: Vec2, h: Vec2) -> f32 {
|
||||
far_side(p.x, h.x).max(far_side(p.y, h.y))
|
||||
}
|
||||
|
||||
@ -638,25 +638,25 @@ impl RadialGradientShape {
|
||||
reflect(Serialize, Deserialize)
|
||||
)]
|
||||
pub enum InterpolationColorSpace {
|
||||
/// Interpolates in `OKLab` space.
|
||||
/// Interpolates in OKLABA space.
|
||||
#[default]
|
||||
OkLab,
|
||||
/// Interpolates in OKLCH space, taking the shortest hue path.
|
||||
OkLch,
|
||||
/// Interpolates in OKLCH space, taking the longest hue path.
|
||||
OkLchLong,
|
||||
/// Interpolates in sRGB space.
|
||||
Srgb,
|
||||
/// Interpolates in linear sRGB space.
|
||||
LinearRgb,
|
||||
/// Interpolates in HSL space, taking the shortest hue path.
|
||||
Hsl,
|
||||
/// Interpolates in HSL space, taking the longest hue path.
|
||||
HslLong,
|
||||
/// Interpolates in HSV space, taking the shortest hue path.
|
||||
Hsv,
|
||||
/// Interpolates in HSV space, taking the longest hue path.
|
||||
HsvLong,
|
||||
Oklaba,
|
||||
/// Interpolates in OKLCHA space, taking the shortest hue path.
|
||||
Oklcha,
|
||||
/// Interpolates in OKLCHA space, taking the longest hue path.
|
||||
OklchaLong,
|
||||
/// Interpolates in sRGBA space.
|
||||
Srgba,
|
||||
/// Interpolates in linear sRGBA space.
|
||||
LinearRgba,
|
||||
/// Interpolates in HSLA space, taking the shortest hue path.
|
||||
Hsla,
|
||||
/// Interpolates in HSLA space, taking the longest hue path.
|
||||
HslaLong,
|
||||
/// Interpolates in HSVA space, taking the shortest hue path.
|
||||
Hsva,
|
||||
/// Interpolates in HSVA space, taking the longest hue path.
|
||||
HsvaLong,
|
||||
}
|
||||
|
||||
/// Set the color space used for interpolation.
|
||||
@ -665,28 +665,28 @@ pub trait InColorSpace: Sized {
|
||||
fn in_color_space(self, color_space: InterpolationColorSpace) -> Self;
|
||||
|
||||
/// Interpolate in `OKLab` space.
|
||||
fn in_oklab(self) -> Self {
|
||||
self.in_color_space(InterpolationColorSpace::OkLab)
|
||||
fn in_oklaba(self) -> Self {
|
||||
self.in_color_space(InterpolationColorSpace::Oklaba)
|
||||
}
|
||||
|
||||
/// Interpolate in OKLCH space (short hue path).
|
||||
fn in_oklch(self) -> Self {
|
||||
self.in_color_space(InterpolationColorSpace::OkLch)
|
||||
self.in_color_space(InterpolationColorSpace::Oklcha)
|
||||
}
|
||||
|
||||
/// Interpolate in OKLCH space (long hue path).
|
||||
fn in_oklch_long(self) -> Self {
|
||||
self.in_color_space(InterpolationColorSpace::OkLchLong)
|
||||
self.in_color_space(InterpolationColorSpace::OklchaLong)
|
||||
}
|
||||
|
||||
/// Interpolate in sRGB space.
|
||||
fn in_srgb(self) -> Self {
|
||||
self.in_color_space(InterpolationColorSpace::Srgb)
|
||||
self.in_color_space(InterpolationColorSpace::Srgba)
|
||||
}
|
||||
|
||||
/// Interpolate in linear sRGB space.
|
||||
fn in_linear_rgb(self) -> Self {
|
||||
self.in_color_space(InterpolationColorSpace::LinearRgb)
|
||||
self.in_color_space(InterpolationColorSpace::LinearRgba)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -181,15 +181,15 @@ impl SpecializedRenderPipeline for GradientPipeline {
|
||||
],
|
||||
);
|
||||
let color_space = match key.color_space {
|
||||
InterpolationColorSpace::OkLab => "IN_OKLAB",
|
||||
InterpolationColorSpace::OkLch => "IN_OKLCH",
|
||||
InterpolationColorSpace::OkLchLong => "IN_OKLCH_LONG",
|
||||
InterpolationColorSpace::Srgb => "IN_SRGB",
|
||||
InterpolationColorSpace::LinearRgb => "IN_LINEAR_RGB",
|
||||
InterpolationColorSpace::Hsl => "IN_HSL",
|
||||
InterpolationColorSpace::HslLong => "IN_HSL_LONG",
|
||||
InterpolationColorSpace::Hsv => "IN_HSV",
|
||||
InterpolationColorSpace::HsvLong => "IN_HSV_LONG",
|
||||
InterpolationColorSpace::Oklaba => "IN_OKLAB",
|
||||
InterpolationColorSpace::Oklcha => "IN_OKLCH",
|
||||
InterpolationColorSpace::OklchaLong => "IN_OKLCH_LONG",
|
||||
InterpolationColorSpace::Srgba => "IN_SRGB",
|
||||
InterpolationColorSpace::LinearRgba => "IN_LINEAR_RGB",
|
||||
InterpolationColorSpace::Hsla => "IN_HSL",
|
||||
InterpolationColorSpace::HslaLong => "IN_HSL_LONG",
|
||||
InterpolationColorSpace::Hsva => "IN_HSV",
|
||||
InterpolationColorSpace::HsvaLong => "IN_HSV_LONG",
|
||||
};
|
||||
|
||||
let shader_defs = if key.anti_alias {
|
||||
|
@ -1,8 +1,5 @@
|
||||
use alloc::string::String;
|
||||
use bevy_ecs::{
|
||||
entity::Entity,
|
||||
event::{BufferedEvent, Event},
|
||||
};
|
||||
use bevy_ecs::{entity::Entity, event::BufferedEvent};
|
||||
use bevy_input::{
|
||||
gestures::*,
|
||||
keyboard::{KeyboardFocusLost, KeyboardInput},
|
||||
@ -26,7 +23,7 @@ use bevy_reflect::{ReflectDeserialize, ReflectSerialize};
|
||||
use crate::WindowTheme;
|
||||
|
||||
/// A window event that is sent whenever a window's logical size has changed.
|
||||
#[derive(Event, BufferedEvent, Debug, Clone, PartialEq)]
|
||||
#[derive(BufferedEvent, Debug, Clone, PartialEq)]
|
||||
#[cfg_attr(
|
||||
feature = "bevy_reflect",
|
||||
derive(Reflect),
|
||||
@ -48,7 +45,7 @@ pub struct WindowResized {
|
||||
|
||||
/// An event that indicates all of the application's windows should be redrawn,
|
||||
/// even if their control flow is set to `Wait` and there have been no window events.
|
||||
#[derive(Event, BufferedEvent, Debug, Clone, PartialEq, Eq)]
|
||||
#[derive(BufferedEvent, Debug, Clone, PartialEq, Eq)]
|
||||
#[cfg_attr(
|
||||
feature = "bevy_reflect",
|
||||
derive(Reflect),
|
||||
@ -64,7 +61,7 @@ pub struct RequestRedraw;
|
||||
/// An event that is sent whenever a new window is created.
|
||||
///
|
||||
/// To create a new window, spawn an entity with a [`crate::Window`] on it.
|
||||
#[derive(Event, BufferedEvent, Debug, Clone, PartialEq, Eq)]
|
||||
#[derive(BufferedEvent, Debug, Clone, PartialEq, Eq)]
|
||||
#[cfg_attr(
|
||||
feature = "bevy_reflect",
|
||||
derive(Reflect),
|
||||
@ -90,7 +87,7 @@ pub struct WindowCreated {
|
||||
///
|
||||
/// [`WindowPlugin`]: crate::WindowPlugin
|
||||
/// [`Window`]: crate::Window
|
||||
#[derive(Event, BufferedEvent, Debug, Clone, PartialEq, Eq)]
|
||||
#[derive(BufferedEvent, Debug, Clone, PartialEq, Eq)]
|
||||
#[cfg_attr(
|
||||
feature = "bevy_reflect",
|
||||
derive(Reflect),
|
||||
@ -108,7 +105,7 @@ pub struct WindowCloseRequested {
|
||||
|
||||
/// An event that is sent whenever a window is closed. This will be sent when
|
||||
/// the window entity loses its [`Window`](crate::window::Window) component or is despawned.
|
||||
#[derive(Event, BufferedEvent, Debug, Clone, PartialEq, Eq)]
|
||||
#[derive(BufferedEvent, Debug, Clone, PartialEq, Eq)]
|
||||
#[cfg_attr(
|
||||
feature = "bevy_reflect",
|
||||
derive(Reflect),
|
||||
@ -129,7 +126,7 @@ pub struct WindowClosed {
|
||||
|
||||
/// An event that is sent whenever a window is closing. This will be sent when
|
||||
/// after a [`WindowCloseRequested`] event is received and the window is in the process of closing.
|
||||
#[derive(Event, BufferedEvent, Debug, Clone, PartialEq, Eq)]
|
||||
#[derive(BufferedEvent, Debug, Clone, PartialEq, Eq)]
|
||||
#[cfg_attr(
|
||||
feature = "bevy_reflect",
|
||||
derive(Reflect),
|
||||
@ -149,7 +146,7 @@ pub struct WindowClosing {
|
||||
///
|
||||
/// Note that if your application only has a single window, this event may be your last chance to
|
||||
/// persist state before the application terminates.
|
||||
#[derive(Event, BufferedEvent, Debug, Clone, PartialEq, Eq)]
|
||||
#[derive(BufferedEvent, Debug, Clone, PartialEq, Eq)]
|
||||
#[cfg_attr(
|
||||
feature = "bevy_reflect",
|
||||
derive(Reflect),
|
||||
@ -179,7 +176,7 @@ pub struct WindowDestroyed {
|
||||
/// you should not use it for non-cursor-like behavior such as 3D camera control. Please see `MouseMotion` instead.
|
||||
///
|
||||
/// [`WindowEvent::CursorMoved`]: https://docs.rs/winit/latest/winit/event/enum.WindowEvent.html#variant.CursorMoved
|
||||
#[derive(Event, BufferedEvent, Debug, Clone, PartialEq)]
|
||||
#[derive(BufferedEvent, Debug, Clone, PartialEq)]
|
||||
#[cfg_attr(
|
||||
feature = "bevy_reflect",
|
||||
derive(Reflect),
|
||||
@ -204,7 +201,7 @@ pub struct CursorMoved {
|
||||
}
|
||||
|
||||
/// An event that is sent whenever the user's cursor enters a window.
|
||||
#[derive(Event, BufferedEvent, Debug, Clone, PartialEq, Eq)]
|
||||
#[derive(BufferedEvent, Debug, Clone, PartialEq, Eq)]
|
||||
#[cfg_attr(
|
||||
feature = "bevy_reflect",
|
||||
derive(Reflect),
|
||||
@ -221,7 +218,7 @@ pub struct CursorEntered {
|
||||
}
|
||||
|
||||
/// An event that is sent whenever the user's cursor leaves a window.
|
||||
#[derive(Event, BufferedEvent, Debug, Clone, PartialEq, Eq)]
|
||||
#[derive(BufferedEvent, Debug, Clone, PartialEq, Eq)]
|
||||
#[cfg_attr(
|
||||
feature = "bevy_reflect",
|
||||
derive(Reflect),
|
||||
@ -242,7 +239,7 @@ pub struct CursorLeft {
|
||||
/// This event is the translated version of the `WindowEvent::Ime` from the `winit` crate.
|
||||
///
|
||||
/// It is only sent if IME was enabled on the window with [`Window::ime_enabled`](crate::window::Window::ime_enabled).
|
||||
#[derive(Event, BufferedEvent, Debug, Clone, PartialEq, Eq)]
|
||||
#[derive(BufferedEvent, Debug, Clone, PartialEq, Eq)]
|
||||
#[cfg_attr(
|
||||
feature = "bevy_reflect",
|
||||
derive(Reflect),
|
||||
@ -287,7 +284,7 @@ pub enum Ime {
|
||||
}
|
||||
|
||||
/// An event that indicates a window has received or lost focus.
|
||||
#[derive(Event, BufferedEvent, Debug, Clone, PartialEq, Eq)]
|
||||
#[derive(BufferedEvent, Debug, Clone, PartialEq, Eq)]
|
||||
#[cfg_attr(
|
||||
feature = "bevy_reflect",
|
||||
derive(Reflect),
|
||||
@ -314,7 +311,7 @@ pub struct WindowFocused {
|
||||
/// It is the translated version of [`WindowEvent::Occluded`] from the `winit` crate.
|
||||
///
|
||||
/// [`WindowEvent::Occluded`]: https://docs.rs/winit/latest/winit/event/enum.WindowEvent.html#variant.Occluded
|
||||
#[derive(Event, BufferedEvent, Debug, Clone, PartialEq, Eq)]
|
||||
#[derive(BufferedEvent, Debug, Clone, PartialEq, Eq)]
|
||||
#[cfg_attr(
|
||||
feature = "bevy_reflect",
|
||||
derive(Reflect),
|
||||
@ -333,7 +330,7 @@ pub struct WindowOccluded {
|
||||
}
|
||||
|
||||
/// An event that indicates a window's scale factor has changed.
|
||||
#[derive(Event, BufferedEvent, Debug, Clone, PartialEq)]
|
||||
#[derive(BufferedEvent, Debug, Clone, PartialEq)]
|
||||
#[cfg_attr(
|
||||
feature = "bevy_reflect",
|
||||
derive(Reflect),
|
||||
@ -352,7 +349,7 @@ pub struct WindowScaleFactorChanged {
|
||||
}
|
||||
|
||||
/// An event that indicates a window's OS-reported scale factor has changed.
|
||||
#[derive(Event, BufferedEvent, Debug, Clone, PartialEq)]
|
||||
#[derive(BufferedEvent, Debug, Clone, PartialEq)]
|
||||
#[cfg_attr(
|
||||
feature = "bevy_reflect",
|
||||
derive(Reflect),
|
||||
@ -371,7 +368,7 @@ pub struct WindowBackendScaleFactorChanged {
|
||||
}
|
||||
|
||||
/// Events related to files being dragged and dropped on a window.
|
||||
#[derive(Event, BufferedEvent, Debug, Clone, PartialEq, Eq)]
|
||||
#[derive(BufferedEvent, Debug, Clone, PartialEq, Eq)]
|
||||
#[cfg_attr(
|
||||
feature = "bevy_reflect",
|
||||
derive(Reflect),
|
||||
@ -407,7 +404,7 @@ pub enum FileDragAndDrop {
|
||||
}
|
||||
|
||||
/// An event that is sent when a window is repositioned in physical pixels.
|
||||
#[derive(Event, BufferedEvent, Debug, Clone, PartialEq, Eq)]
|
||||
#[derive(BufferedEvent, Debug, Clone, PartialEq, Eq)]
|
||||
#[cfg_attr(
|
||||
feature = "bevy_reflect",
|
||||
derive(Reflect),
|
||||
@ -429,7 +426,7 @@ pub struct WindowMoved {
|
||||
///
|
||||
/// This event is only sent when the window is relying on the system theme to control its appearance.
|
||||
/// i.e. It is only sent when [`Window::window_theme`](crate::window::Window::window_theme) is `None` and the system theme changes.
|
||||
#[derive(Event, BufferedEvent, Debug, Clone, PartialEq, Eq)]
|
||||
#[derive(BufferedEvent, Debug, Clone, PartialEq, Eq)]
|
||||
#[cfg_attr(
|
||||
feature = "bevy_reflect",
|
||||
derive(Reflect),
|
||||
@ -448,7 +445,7 @@ pub struct WindowThemeChanged {
|
||||
}
|
||||
|
||||
/// Application lifetime events
|
||||
#[derive(Event, BufferedEvent, Debug, Clone, Copy, PartialEq, Eq)]
|
||||
#[derive(BufferedEvent, Debug, Clone, Copy, PartialEq, Eq)]
|
||||
#[cfg_attr(
|
||||
feature = "bevy_reflect",
|
||||
derive(Reflect),
|
||||
@ -491,7 +488,7 @@ impl AppLifecycle {
|
||||
/// access window events in the order they were received from the
|
||||
/// operating system. Otherwise, the event types are individually
|
||||
/// readable with `EventReader<E>` (e.g. `EventReader<KeyboardInput>`).
|
||||
#[derive(Event, BufferedEvent, Debug, Clone, PartialEq)]
|
||||
#[derive(BufferedEvent, Debug, Clone, PartialEq)]
|
||||
#[cfg_attr(
|
||||
feature = "bevy_reflect",
|
||||
derive(Reflect),
|
||||
|
@ -156,7 +156,7 @@ impl<T: BufferedEvent> Plugin for WinitPlugin<T> {
|
||||
|
||||
/// The default event that can be used to wake the window loop
|
||||
/// Wakes up the loop if in wait state
|
||||
#[derive(Debug, Default, Clone, Copy, Event, BufferedEvent, Reflect)]
|
||||
#[derive(Debug, Default, Clone, Copy, BufferedEvent, Reflect)]
|
||||
#[reflect(Debug, Default, Clone)]
|
||||
pub struct WakeUp;
|
||||
|
||||
@ -167,7 +167,7 @@ pub struct WakeUp;
|
||||
///
|
||||
/// When you receive this event it has already been handled by Bevy's main loop.
|
||||
/// Sending these events will NOT cause them to be processed by Bevy.
|
||||
#[derive(Debug, Clone, Event, BufferedEvent)]
|
||||
#[derive(Debug, Clone, BufferedEvent)]
|
||||
pub struct RawWinitWindowEvent {
|
||||
/// The window for which the event was fired.
|
||||
pub window_id: WindowId,
|
||||
|
@ -67,9 +67,14 @@ The [Tracy profiling tool](https://github.com/wolfpld/tracy) is:
|
||||
|
||||
There are binaries available for Windows, and installation / build instructions for other operating systems can be found in the [Tracy documentation PDF](https://github.com/wolfpld/tracy/releases/latest/download/tracy.pdf).
|
||||
|
||||
To determine which Tracy version to install
|
||||
|
||||
1. Run `cargo tree --features bevy/trace_tracy | grep tracy` in your Bevy workspace root to see which tracy dep versions are used
|
||||
2. Cross reference the tracy dep versions with the [Version Support Table](https://github.com/nagisa/rust_tracy_client?tab=readme-ov-file#version-support-table)
|
||||
|
||||
It has a command line capture tool that can record the execution of graphical applications, saving it as a profile file. Tracy has a GUI to inspect these profile files. The GUI app also supports live capture, showing you in real time the trace of your app. The version of tracy must be matched to the version of tracing-tracy used in bevy. A compatibility table can be found on [crates.io](https://crates.io/crates/tracing-tracy) and the version used can be found [here](https://github.com/bevyengine/bevy/blob/latest/crates/bevy_log/Cargo.toml).
|
||||
|
||||
On macOS, Tracy can be installed through Homebrew by running `brew install tracy`, and the GUI client can be launched by running `tracy`.
|
||||
On macOS, Tracy can be installed through Homebrew by running `brew install tracy`, and the GUI client can be launched by running `tracy`. Note that `brew` does not always have the latest version of Tracy available, in which cases you may be required to build from source.
|
||||
|
||||
In one terminal, run:
|
||||
`./capture-release -o my_capture.tracy`
|
||||
@ -154,20 +159,20 @@ Follow the steps below to start GPU debugging on macOS. There is no need to crea
|
||||
|
||||
1. In the menu bar click on Debug > Debug Executable…
|
||||
|
||||

|
||||

|
||||
|
||||
2. Select your executable from your project’s target folder.
|
||||
3. The Scheme Editor will open. If your assets are not located next to your executable, you can go to the Arguments tab and set `BEVY_ASSET_ROOT` to the absolute path for your project (the parent of your assets folder). The rest of the defaults should be fine.
|
||||
|
||||

|
||||

|
||||
|
||||
4. Click the play button in the top left and this should start your bevy app.
|
||||
|
||||

|
||||

|
||||
|
||||
5. Go back to Xcode and click on the Metal icon in the bottom drawer and then Capture in the following the popup menu.
|
||||
|
||||

|
||||

|
||||
|
||||
6. Start debugging and profiling!
|
||||
|
||||
@ -183,6 +188,7 @@ When you compile with Bevy's `trace_tracy` feature, GPU spans will show up in a
|
||||
|
||||
> [!NOTE]
|
||||
> Due to dynamic clock speeds, GPU timings will have large frame-to-frame variance, unless you use an external tool to lock your GPU clocks to base speeds. When measuring GPU performance via Tracy, only look at the MTPC column of Tracy's statistics panel, or the span distribution/median, and not at any individual frame data.
|
||||
|
||||
<!-- markdownlint-disable MD028 -->
|
||||
|
||||
> [!NOTE]
|
||||
|
@ -21,49 +21,32 @@ fn main() {
|
||||
|
||||
fn draw_cursor(
|
||||
camera_query: Single<(&Camera, &GlobalTransform)>,
|
||||
window: Query<&Window>,
|
||||
window: Single<&Window>,
|
||||
mut gizmos: Gizmos,
|
||||
) {
|
||||
let (camera, camera_transform) = *camera_query;
|
||||
let Ok(window) = window.single() else {
|
||||
return;
|
||||
};
|
||||
|
||||
let Some(cursor_position) = window.cursor_position() else {
|
||||
return;
|
||||
};
|
||||
|
||||
// Calculate a world position based on the cursor's position.
|
||||
let Ok(world_pos) = camera.viewport_to_world_2d(camera_transform, cursor_position) else {
|
||||
return;
|
||||
};
|
||||
|
||||
// To test Camera::world_to_viewport, convert result back to viewport space and then back to world space.
|
||||
let Ok(viewport_check) = camera.world_to_viewport(camera_transform, world_pos.extend(0.0))
|
||||
else {
|
||||
return;
|
||||
};
|
||||
let Ok(world_check) = camera.viewport_to_world_2d(camera_transform, viewport_check.xy()) else {
|
||||
return;
|
||||
};
|
||||
|
||||
gizmos.circle_2d(world_pos, 10., WHITE);
|
||||
// Should be the same as world_pos
|
||||
gizmos.circle_2d(world_check, 8., RED);
|
||||
if let Some(cursor_position) = window.cursor_position()
|
||||
// Calculate a world position based on the cursor's position.
|
||||
&& let Ok(world_pos) = camera.viewport_to_world_2d(camera_transform, cursor_position)
|
||||
// To test Camera::world_to_viewport, convert result back to viewport space and then back to world space.
|
||||
&& let Ok(viewport_check) = camera.world_to_viewport(camera_transform, world_pos.extend(0.0))
|
||||
&& let Ok(world_check) = camera.viewport_to_world_2d(camera_transform, viewport_check.xy())
|
||||
{
|
||||
gizmos.circle_2d(world_pos, 10., WHITE);
|
||||
// Should be the same as world_pos
|
||||
gizmos.circle_2d(world_check, 8., RED);
|
||||
}
|
||||
}
|
||||
|
||||
fn controls(
|
||||
mut camera_query: Query<(&mut Camera, &mut Transform, &mut Projection)>,
|
||||
window: Query<&Window>,
|
||||
camera_query: Single<(&mut Camera, &mut Transform, &mut Projection)>,
|
||||
window: Single<&Window>,
|
||||
input: Res<ButtonInput<KeyCode>>,
|
||||
time: Res<Time<Fixed>>,
|
||||
) {
|
||||
let Ok(window) = window.single() else {
|
||||
return;
|
||||
};
|
||||
let Ok((mut camera, mut transform, mut projection)) = camera_query.single_mut() else {
|
||||
return;
|
||||
};
|
||||
let (mut camera, mut transform, mut projection) = camera_query.into_inner();
|
||||
|
||||
let fspeed = 600.0 * time.delta_secs();
|
||||
let uspeed = fspeed as u32;
|
||||
let window_size = window.resolution.physical_size();
|
||||
|
@ -27,7 +27,7 @@ use bevy::{
|
||||
sync_component::SyncComponentPlugin,
|
||||
sync_world::{MainEntityHashMap, RenderEntity},
|
||||
view::{ExtractedView, RenderVisibleEntities, ViewTarget},
|
||||
Extract, Render, RenderApp, RenderSystems,
|
||||
Extract, Render, RenderApp, RenderStartup, RenderSystems,
|
||||
},
|
||||
sprite::{
|
||||
extract_mesh2d, DrawMesh2d, Material2dBindGroupId, Mesh2dPipeline, Mesh2dPipelineKey,
|
||||
@ -132,14 +132,16 @@ pub struct ColoredMesh2dPipeline {
|
||||
shader: Handle<Shader>,
|
||||
}
|
||||
|
||||
impl FromWorld for ColoredMesh2dPipeline {
|
||||
fn from_world(world: &mut World) -> Self {
|
||||
Self {
|
||||
mesh2d_pipeline: Mesh2dPipeline::from_world(world),
|
||||
// Get the shader from the shader resource we inserted in the plugin.
|
||||
shader: world.resource::<ColoredMesh2dShader>().0.clone(),
|
||||
}
|
||||
}
|
||||
fn init_colored_mesh_2d_pipeline(
|
||||
mut commands: Commands,
|
||||
mesh2d_pipeline: Res<Mesh2dPipeline>,
|
||||
colored_mesh2d_shader: Res<ColoredMesh2dShader>,
|
||||
) {
|
||||
commands.insert_resource(ColoredMesh2dPipeline {
|
||||
mesh2d_pipeline: mesh2d_pipeline.clone(),
|
||||
// Clone the shader from the shader resource we inserted in the plugin.
|
||||
shader: colored_mesh2d_shader.0.clone(),
|
||||
});
|
||||
}
|
||||
|
||||
// We implement `SpecializedPipeline` to customize the default rendering from `Mesh2dPipeline`
|
||||
@ -307,6 +309,7 @@ impl Plugin for ColoredMesh2dPlugin {
|
||||
.add_render_command::<Transparent2d, DrawColoredMesh2d>()
|
||||
.init_resource::<SpecializedRenderPipelines<ColoredMesh2dPipeline>>()
|
||||
.init_resource::<RenderColoredMesh2dInstances>()
|
||||
.add_systems(RenderStartup, init_colored_mesh_2d_pipeline)
|
||||
.add_systems(
|
||||
ExtractSchedule,
|
||||
extract_colored_mesh2d.after(extract_mesh2d),
|
||||
@ -316,13 +319,6 @@ impl Plugin for ColoredMesh2dPlugin {
|
||||
queue_colored_mesh2d.in_set(RenderSystems::QueueMeshes),
|
||||
);
|
||||
}
|
||||
|
||||
fn finish(&self, app: &mut App) {
|
||||
// Register our custom pipeline
|
||||
app.get_sub_app_mut(RenderApp)
|
||||
.unwrap()
|
||||
.init_resource::<ColoredMesh2dPipeline>();
|
||||
}
|
||||
}
|
||||
|
||||
/// Extract the [`ColoredMesh2d`] marker component into the render app
|
||||
|
@ -29,7 +29,7 @@ use bevy::{
|
||||
sync_world::MainEntity,
|
||||
texture::GpuImage,
|
||||
view::ExtractedView,
|
||||
Extract, RenderApp,
|
||||
Extract, RenderApp, RenderStartup,
|
||||
},
|
||||
utils::Parallel,
|
||||
};
|
||||
@ -55,54 +55,48 @@ impl Plugin for ImageMaterialPlugin {
|
||||
check_entities_needing_specialization.after(AssetEventSystems),
|
||||
)
|
||||
.init_resource::<EntitiesNeedingSpecialization<ImageMaterial>>();
|
||||
}
|
||||
|
||||
fn finish(&self, app: &mut App) {
|
||||
let Some(render_app) = app.get_sub_app_mut(RenderApp) else {
|
||||
return;
|
||||
};
|
||||
|
||||
render_app.add_systems(
|
||||
ExtractSchedule,
|
||||
(
|
||||
extract_image_materials,
|
||||
extract_image_materials_needing_specialization,
|
||||
),
|
||||
);
|
||||
|
||||
render_app.world_mut().resource_scope(
|
||||
|world: &mut World, mut bind_group_allocators: Mut<MaterialBindGroupAllocators>| {
|
||||
world.resource_scope(|world: &mut World, render_device: Mut<RenderDevice>| {
|
||||
let bind_group_layout = render_device.create_bind_group_layout(
|
||||
"image_material_layout",
|
||||
&BindGroupLayoutEntries::sequential(
|
||||
ShaderStages::FRAGMENT,
|
||||
(
|
||||
texture_2d(TextureSampleType::Float { filterable: false }),
|
||||
sampler(SamplerBindingType::NonFiltering),
|
||||
),
|
||||
),
|
||||
);
|
||||
let sampler = render_device.create_sampler(&SamplerDescriptor::default());
|
||||
world.insert_resource(ImageMaterialBindGroupLayout(bind_group_layout.clone()));
|
||||
world.insert_resource(ImageMaterialBindGroupSampler(sampler));
|
||||
|
||||
bind_group_allocators.insert(
|
||||
TypeId::of::<ImageMaterial>(),
|
||||
MaterialBindGroupAllocator::new(
|
||||
&render_device,
|
||||
None,
|
||||
None,
|
||||
bind_group_layout,
|
||||
None,
|
||||
),
|
||||
);
|
||||
});
|
||||
},
|
||||
);
|
||||
render_app
|
||||
.add_systems(RenderStartup, init_image_material_resources)
|
||||
.add_systems(
|
||||
ExtractSchedule,
|
||||
(
|
||||
extract_image_materials,
|
||||
extract_image_materials_needing_specialization,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
fn init_image_material_resources(
|
||||
mut commands: Commands,
|
||||
render_device: Res<RenderDevice>,
|
||||
mut bind_group_allocators: ResMut<MaterialBindGroupAllocators>,
|
||||
) {
|
||||
let bind_group_layout = render_device.create_bind_group_layout(
|
||||
"image_material_layout",
|
||||
&BindGroupLayoutEntries::sequential(
|
||||
ShaderStages::FRAGMENT,
|
||||
(
|
||||
texture_2d(TextureSampleType::Float { filterable: false }),
|
||||
sampler(SamplerBindingType::NonFiltering),
|
||||
),
|
||||
),
|
||||
);
|
||||
let sampler = render_device.create_sampler(&SamplerDescriptor::default());
|
||||
commands.insert_resource(ImageMaterialBindGroupLayout(bind_group_layout.clone()));
|
||||
commands.insert_resource(ImageMaterialBindGroupSampler(sampler));
|
||||
|
||||
bind_group_allocators.insert(
|
||||
TypeId::of::<ImageMaterial>(),
|
||||
MaterialBindGroupAllocator::new(&render_device, None, None, bind_group_layout, None),
|
||||
);
|
||||
}
|
||||
|
||||
#[derive(Resource)]
|
||||
struct ImageMaterialBindGroupLayout(BindGroupLayout);
|
||||
|
||||
|
@ -72,7 +72,7 @@ enum LightingMode {
|
||||
/// An event that's fired whenever the user changes the lighting mode.
|
||||
///
|
||||
/// This is also fired when the scene loads for the first time.
|
||||
#[derive(Clone, Copy, Default, Event, BufferedEvent)]
|
||||
#[derive(Clone, Copy, Default, BufferedEvent)]
|
||||
struct LightingModeChanged;
|
||||
|
||||
#[derive(Clone, Copy, Component, Debug)]
|
||||
|
@ -38,7 +38,7 @@ fn main() {
|
||||
}
|
||||
|
||||
/// A basic message. This is what we will be sending from the [`CaptureLayer`] to [`CapturedLogEvents`] non-send resource.
|
||||
#[derive(Debug, Event, BufferedEvent)]
|
||||
#[derive(Debug, BufferedEvent)]
|
||||
struct LogEvent {
|
||||
message: String,
|
||||
level: Level,
|
||||
|
@ -20,7 +20,7 @@ fn main() {
|
||||
#[derive(Resource, Deref)]
|
||||
struct StreamReceiver(Receiver<u32>);
|
||||
|
||||
#[derive(Event, BufferedEvent)]
|
||||
#[derive(BufferedEvent)]
|
||||
struct StreamEvent(u32);
|
||||
|
||||
fn setup(mut commands: Commands) {
|
||||
|
@ -12,7 +12,7 @@ fn main() {
|
||||
.run();
|
||||
}
|
||||
|
||||
#[derive(Event, BufferedEvent, Default)]
|
||||
#[derive(BufferedEvent, Default)]
|
||||
struct PlayPitch;
|
||||
|
||||
#[derive(Resource)]
|
||||
|
@ -45,7 +45,7 @@ impl Component for MyComponent {
|
||||
#[derive(Resource, Default, Debug, Deref, DerefMut)]
|
||||
struct MyComponentIndex(HashMap<KeyCode, Entity>);
|
||||
|
||||
#[derive(Event, BufferedEvent)]
|
||||
#[derive(BufferedEvent)]
|
||||
struct MyEvent;
|
||||
|
||||
fn main() {
|
||||
|
@ -6,17 +6,17 @@ use bevy::prelude::*;
|
||||
|
||||
// In order to send or receive events first you must define them
|
||||
// This event should be sent when something attempts to deal damage to another entity.
|
||||
#[derive(Event, BufferedEvent, Debug)]
|
||||
#[derive(BufferedEvent, Debug)]
|
||||
struct DealDamage {
|
||||
pub amount: i32,
|
||||
}
|
||||
|
||||
// This event should be sent when an entity receives damage.
|
||||
#[derive(Event, BufferedEvent, Debug, Default)]
|
||||
#[derive(BufferedEvent, Debug, Default)]
|
||||
struct DamageReceived;
|
||||
|
||||
// This event should be sent when an entity blocks damage with armor.
|
||||
#[derive(Event, BufferedEvent, Debug, Default)]
|
||||
#[derive(BufferedEvent, Debug, Default)]
|
||||
struct ArmorBlockedDamage;
|
||||
|
||||
// This resource represents a timer used to determine when to deal damage
|
||||
|
@ -46,10 +46,10 @@ fn main() {
|
||||
app.update();
|
||||
}
|
||||
|
||||
#[derive(Event, BufferedEvent)]
|
||||
#[derive(BufferedEvent)]
|
||||
struct A;
|
||||
|
||||
#[derive(Event, BufferedEvent)]
|
||||
#[derive(BufferedEvent)]
|
||||
struct B;
|
||||
|
||||
// This works fine, because the types are different,
|
||||
@ -62,7 +62,7 @@ fn read_and_write_different_event_types(mut a: EventWriter<A>, mut b: EventReade
|
||||
}
|
||||
|
||||
/// A dummy event type.
|
||||
#[derive(Debug, Clone, Event, BufferedEvent)]
|
||||
#[derive(Debug, Clone, BufferedEvent)]
|
||||
struct DebugEvent {
|
||||
resend_from_param_set: bool,
|
||||
resend_from_local_event_reader: bool,
|
||||
|
@ -89,7 +89,7 @@ struct Ball;
|
||||
#[derive(Component, Deref, DerefMut)]
|
||||
struct Velocity(Vec2);
|
||||
|
||||
#[derive(Event, BufferedEvent, Default)]
|
||||
#[derive(BufferedEvent, Default)]
|
||||
struct CollisionEvent;
|
||||
|
||||
#[derive(Component)]
|
||||
|
@ -6,7 +6,7 @@ use bevy::{ecs::system::EntityCommands, prelude::*};
|
||||
|
||||
/// An event that's sent whenever the user changes one of the settings by
|
||||
/// clicking a radio button.
|
||||
#[derive(Clone, Event, BufferedEvent, Deref, DerefMut)]
|
||||
#[derive(Clone, BufferedEvent, Deref, DerefMut)]
|
||||
pub struct WidgetClickEvent<T>(T);
|
||||
|
||||
/// A marker component that we place on all widgets that send
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user