pub trait Specializer<T>:
Send
+ Sync
+ 'staticwhere
T: Specializable,{
type Key: SpecializerKey;
// Required method
fn specialize(
&self,
key: Self::Key,
descriptor: &mut <T as Specializable>::Descriptor,
) -> Result<<Self::Key as SpecializerKey>::Canonical, BevyError>;
}
Expand description
Defines a type capable of “specializing” values of a type T.
Specialization is the process of generating variants of a type T
from small hashable keys, and specializers themselves can be
thought of as pure functions from the key type to T
, that
memoize their results based on the key.
T
rather
than produce T
itself, but the above comparison is still valid.
Since compiling render and compute pipelines can be so slow,
specialization allows a Bevy app to detect when it would compile
a duplicate pipeline and reuse what’s already in the cache. While
pipelines could all be memoized hashing each whole descriptor, this
would be much slower and could still create duplicates. In contrast,
memoizing groups of related pipelines based on a small hashable
key is much faster. See the docs on SpecializerKey
for more info.
§Composing Specializers
This trait can be derived with #[derive(Specializer)]
for structs whose
fields all implement Specializer
. This allows for composing multiple
specializers together, and makes encapsulation and separating concerns
between specializers much nicer. One could make individual specializers
for common operations and place them in entirely separate modules, then
compose them together with a single #[derive]
struct A;
struct B;
#[derive(Copy, Clone, PartialEq, Eq, Hash, SpecializerKey)]
struct BKey { contrived_number: u32 };
impl Specializer<RenderPipeline> for A {
type Key = ();
fn specialize(
&self,
key: (),
descriptor: &mut RenderPipelineDescriptor
) -> Result<(), BevyError> {
// mutate the descriptor here
Ok(key)
}
}
impl Specializer<RenderPipeline> for B {
type Key = BKey;
fn specialize(
&self,
key: BKey,
descriptor: &mut RenderPipelineDescriptor
) -> Result<BKey, BevyError> {
// mutate the descriptor here
Ok(key)
}
}
#[derive(Specializer)]
#[specialize(RenderPipeline)]
struct C {
#[key(default)]
a: A,
b: B,
}
/*
The generated implementation:
impl Specializer<RenderPipeline> for C {
type Key = BKey;
fn specialize(
&self,
key: Self::Key,
descriptor: &mut RenderPipelineDescriptor
) -> Result<Canonical<Self::Key>, BevyError> {
let _ = self.a.specialize((), descriptor);
let key = self.b.specialize(key, descriptor);
Ok(key)
}
}
*/
The key type for a composed specializer will be a tuple of the keys
of each field, and their specialization logic will be applied in field
order. Since derive macros can’t have generic parameters, the derive macro
requires an additional #[specialize(..targets)]
attribute to specify a
list of types to target for the implementation. #[specialize(all)]
is
also allowed, and will generate a fully generic implementation at the cost
of slightly worse error messages.
Additionally, each field can optionally take a #[key]
attribute to
specify a “key override”. This will hide that field’s key from being
exposed by the wrapper, and always use the value given by the attribute.
Values for this attribute may either be default
which will use the key’s
Default
implementation, or a valid rust expression of the key type.