From ddf6e472bac0a88a3a4a6722513039f6a80969fe Mon Sep 17 00:00:00 2001 From: Emerson Coskey Date: Thu, 31 Jul 2025 22:22:26 -0700 Subject: [PATCH 1/2] add erased specializers --- .../src/render_resource/specializer.rs | 131 +++++++++++++++++- 1 file changed, 128 insertions(+), 3 deletions(-) diff --git a/crates/bevy_render/src/render_resource/specializer.rs b/crates/bevy_render/src/render_resource/specializer.rs index 2dc52577db690..d7400d04916a0 100644 --- a/crates/bevy_render/src/render_resource/specializer.rs +++ b/crates/bevy_render/src/render_resource/specializer.rs @@ -21,7 +21,7 @@ pub use bevy_render_macros::{Specializer, SpecializerKey}; /// likely will not have much utility for other types. /// /// See docs on [`Specializer`] for more info. -pub trait Specializable { +pub trait Specializable: 'static { type Descriptor: PartialEq + Clone + Send + Sync; type CachedId: Clone + Send + Sync; fn queue(pipeline_cache: &PipelineCache, descriptor: Self::Descriptor) -> Self::CachedId; @@ -209,13 +209,13 @@ pub trait Specializer: Send + Sync + 'static { /// differently, they should nearly always produce different descriptors. /// /// [canonical]: https://en.wikipedia.org/wiki/Canonicalization -pub trait SpecializerKey: Clone + Hash + Eq { +pub trait SpecializerKey: Send + Sync + Clone + Hash + Eq + 'static { /// Denotes whether this key is canonical or not. This should only be `true` /// if and only if `Canonical = Self`. const IS_CANONICAL: bool; /// The canonical key type to convert this into during specialization. - type Canonical: Hash + Eq; + type Canonical: SpecializerKey; } pub type Canonical = ::Canonical; @@ -351,3 +351,128 @@ impl> Variants { Ok(id) } } + +pub use dyn_specializer::{ErasedSpecializer, ErasedSpecializerKey}; + +mod dyn_specializer { + use core::{ + any::Any, + hash::{Hash, Hasher}, + }; + + use bevy_ecs::{ + error::BevyError, + label::{DynEq, DynHash}, + }; + use thiserror::Error; + + use super::{Canonical, Specializable, Specializer, SpecializerKey}; + + /// A type-erased wrapper around a `Specializer` + pub trait DynSpecializer: Any + Send + Sync + 'static { + fn dyn_specialize( + &self, + key: ErasedSpecializerKey, + descriptor: &mut T::Descriptor, + ) -> Result; + } + + #[derive(Error, Debug)] + #[error("Incorrect key type passed to an ErasedSpecializer")] + struct IncorrectKeyTypeError; + + impl> DynSpecializer for S { + fn dyn_specialize( + &self, + key: ErasedSpecializerKey, + descriptor: &mut T::Descriptor, + ) -> Result { + let real_key = (&key.0 as &dyn Any) + .downcast_ref::() + .ok_or(IncorrectKeyTypeError)? + .clone(); + let canonical_key = self.specialize(real_key, descriptor)?; + Ok(ErasedSpecializerKey::new(canonical_key)) + } + } + + pub struct ErasedSpecializer(Box>); + + impl ErasedSpecializer { + pub fn new(specializer: impl Specializer) -> Self { + Self(Box::new(specializer)) + } + } + + impl Specializer for ErasedSpecializer { + type Key = ErasedSpecializerKey; + + #[inline] + fn specialize( + &self, + key: Self::Key, + descriptor: &mut T::Descriptor, + ) -> Result, BevyError> { + self.0.dyn_specialize(key, descriptor) + } + } + + pub trait DynSpecializerKey: Send + Sync + DynEq + DynHash { + fn dyn_clone(&self) -> Box; + } + + impl PartialEq for dyn DynSpecializerKey { + fn eq(&self, other: &Self) -> bool { + self.dyn_eq(other) + } + } + + impl Eq for dyn DynSpecializerKey {} + + impl Hash for dyn DynSpecializerKey { + fn hash(&self, state: &mut H) { + self.dyn_hash(state); + } + } + + impl DynSpecializerKey for T { + fn dyn_clone(&self) -> Box { + Box::new(self.clone()) + } + } + + /// A type-erased wrapper around a `SpecializerKey` + pub struct ErasedSpecializerKey(Box); + + impl Clone for ErasedSpecializerKey { + fn clone(&self) -> Self { + Self(self.0.dyn_clone()) + } + } + + impl Hash for ErasedSpecializerKey { + fn hash(&self, state: &mut H) { + self.0.hash(state); + } + } + + impl PartialEq for ErasedSpecializerKey { + fn eq(&self, other: &Self) -> bool { + self.0.dyn_eq(&other.0) + } + } + + impl Eq for ErasedSpecializerKey {} + + impl ErasedSpecializerKey { + pub fn new(key: impl SpecializerKey) -> Self { + Self(Box::new(key)) + } + } + + impl SpecializerKey for ErasedSpecializerKey { + const IS_CANONICAL: bool = false; + + type Canonical = ErasedSpecializerKey; + } +} From 658a42b4b0b0ccc7cf6d2e14614ee6820b919dd4 Mon Sep 17 00:00:00 2001 From: Emerson Coskey Date: Tue, 30 Sep 2025 23:12:40 -0700 Subject: [PATCH 2/2] migration guide --- release-content/migration-guides/specializer_bounds.md | 9 +++++++++ 1 file changed, 9 insertions(+) create mode 100644 release-content/migration-guides/specializer_bounds.md diff --git a/release-content/migration-guides/specializer_bounds.md b/release-content/migration-guides/specializer_bounds.md new file mode 100644 index 0000000000000..61bd723dd6398 --- /dev/null +++ b/release-content/migration-guides/specializer_bounds.md @@ -0,0 +1,9 @@ +--- +title: "Extra bounds on Specializer and SpecializerKey" +pull_requests: [20391] +--- + +- `Specializer` gained a `'static` bound +- `SpecializerKey` gained the following bounds: `Send + Sync + 'static` +- `SpecializerKey::Canonical` is now bounded by `SpecializerKey` + rather than `Hash + Eq`