Skip to content

Commit 64bc811

Browse files
authored
Support operations for render layers and fix equality comparisons (#13310)
# Objective Allow combining render layers with a more-ergonomic syntax than `RenderLayers::from_iter(a.iter().chain(b.iter()))`. ## Solution Add the `or` operation (and corresponding `const` method) to allow computing the union of a set of render layers. While we're here, also added `and` and `xor` operations. Someone might find them useful ## Testing Added a simple unit test.
1 parent 276dd04 commit 64bc811

File tree

1 file changed

+119
-6
lines changed

1 file changed

+119
-6
lines changed

crates/bevy_render/src/view/visibility/render_layers.rs

Lines changed: 119 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,10 @@ pub type Layer = usize;
2222
/// Entities without this component belong to layer `0`.
2323
#[derive(Component, Clone, Reflect, PartialEq, Eq, PartialOrd, Ord)]
2424
#[reflect(Component, Default, PartialEq)]
25-
pub struct RenderLayers(SmallVec<[u64; 1]>);
25+
pub struct RenderLayers(SmallVec<[u64; INLINE_BLOCKS]>);
26+
27+
/// The number of memory blocks stored inline
28+
const INLINE_BLOCKS: usize = 1;
2629

2730
impl Default for &RenderLayers {
2831
fn default() -> Self {
@@ -47,8 +50,7 @@ impl FromIterator<Layer> for RenderLayers {
4750
impl Default for RenderLayers {
4851
/// By default, this structure includes layer `0`, which represents the first layer.
4952
fn default() -> Self {
50-
let (_, bit) = Self::layer_info(0);
51-
RenderLayers(SmallVec::from_const([bit]))
53+
Self::layer(0)
5254
}
5355
}
5456

@@ -57,15 +59,17 @@ impl RenderLayers {
5759
pub const fn layer(n: Layer) -> Self {
5860
let (buffer_index, bit) = Self::layer_info(n);
5961
assert!(
60-
buffer_index < 1,
62+
buffer_index < INLINE_BLOCKS,
6163
"layer is out of bounds for const construction"
6264
);
63-
RenderLayers(SmallVec::from_const([bit]))
65+
let mut buffer = [0; INLINE_BLOCKS];
66+
buffer[buffer_index] = bit;
67+
RenderLayers(SmallVec::from_const(buffer))
6468
}
6569

6670
/// Create a new `RenderLayers` that belongs to no layers.
6771
pub const fn none() -> Self {
68-
RenderLayers(SmallVec::from_const([0]))
72+
RenderLayers(SmallVec::from_const([0; INLINE_BLOCKS]))
6973
}
7074

7175
/// Create a `RenderLayers` from a list of layers.
@@ -91,6 +95,11 @@ impl RenderLayers {
9195
let (buffer_index, bit) = Self::layer_info(layer);
9296
if buffer_index < self.0.len() {
9397
self.0[buffer_index] &= !bit;
98+
// Drop trailing zero memory blocks.
99+
// NOTE: This is not just an optimization, it is necessary for the derived PartialEq impl to be correct.
100+
if buffer_index == self.0.len() - 1 {
101+
self = self.shrink();
102+
}
94103
}
95104
self
96105
}
@@ -154,6 +163,81 @@ impl RenderLayers {
154163
Some(layer - 1)
155164
})
156165
}
166+
167+
/// Returns the set of [layers](Layer) shared by two instances of [`RenderLayers`].
168+
///
169+
/// This corresponds to the `self & other` operation.
170+
pub fn intersection(&self, other: &Self) -> Self {
171+
self.combine_blocks(other, |a, b| a & b).shrink()
172+
}
173+
174+
/// Returns all [layers](Layer) included in either instance of [`RenderLayers`].
175+
///
176+
/// This corresponds to the `self | other` operation.
177+
pub fn union(&self, other: &Self) -> Self {
178+
self.combine_blocks(other, |a, b| a | b) // doesn't need to be shrunk, if the inputs are nonzero then the result will be too
179+
}
180+
181+
/// Returns all [layers](Layer) included in exactly one of the instances of [`RenderLayers`].
182+
///
183+
/// This corresponds to the "exclusive or" (XOR) operation: `self ^ other`.
184+
pub fn symmetric_difference(&self, other: &Self) -> Self {
185+
self.combine_blocks(other, |a, b| a ^ b).shrink()
186+
}
187+
188+
/// Deallocates any trailing-zero memory blocks from this instance
189+
fn shrink(mut self) -> Self {
190+
let mut any_dropped = false;
191+
while self.0.len() > INLINE_BLOCKS && self.0.last() == Some(&0) {
192+
self.0.pop();
193+
any_dropped = true;
194+
}
195+
if any_dropped && self.0.len() <= INLINE_BLOCKS {
196+
self.0.shrink_to_fit();
197+
}
198+
self
199+
}
200+
201+
/// Creates a new instance of [`RenderLayers`] by applying a function to the memory blocks
202+
/// of self and another instance.
203+
///
204+
/// If the function `f` might return `0` for non-zero inputs, you should call [`Self::shrink`]
205+
/// on the output to ensure that there are no trailing zero memory blocks that would break
206+
/// this type's equality comparison.
207+
fn combine_blocks(&self, other: &Self, mut f: impl FnMut(u64, u64) -> u64) -> Self {
208+
let mut a = self.0.iter();
209+
let mut b = other.0.iter();
210+
let mask = std::iter::from_fn(|| {
211+
let a = a.next().copied();
212+
let b = b.next().copied();
213+
if a.is_none() && b.is_none() {
214+
return None;
215+
}
216+
Some(f(a.unwrap_or_default(), b.unwrap_or_default()))
217+
});
218+
Self(mask.collect())
219+
}
220+
}
221+
222+
impl std::ops::BitAnd for RenderLayers {
223+
type Output = Self;
224+
fn bitand(self, rhs: Self) -> Self::Output {
225+
self.intersection(&rhs)
226+
}
227+
}
228+
229+
impl std::ops::BitOr for RenderLayers {
230+
type Output = Self;
231+
fn bitor(self, rhs: Self) -> Self::Output {
232+
self.union(&rhs)
233+
}
234+
}
235+
236+
impl std::ops::BitXor for RenderLayers {
237+
type Output = Self;
238+
fn bitxor(self, rhs: Self) -> Self::Output {
239+
self.symmetric_difference(&rhs)
240+
}
157241
}
158242

159243
#[cfg(test)]
@@ -242,4 +326,33 @@ mod rendering_mask_tests {
242326
let out = layers.iter().collect::<Vec<_>>();
243327
assert_eq!(tricky_layers, out, "tricky layers roundtrip");
244328
}
329+
330+
const MANY: RenderLayers = RenderLayers(SmallVec::from_const([u64::MAX]));
331+
332+
#[test]
333+
fn render_layer_ops() {
334+
let a = RenderLayers::from_layers(&[2, 4, 6]);
335+
let b = RenderLayers::from_layers(&[1, 2, 3, 4, 5]);
336+
337+
assert_eq!(
338+
a.clone() | b.clone(),
339+
RenderLayers::from_layers(&[1, 2, 3, 4, 5, 6])
340+
);
341+
assert_eq!(a.clone() & b.clone(), RenderLayers::from_layers(&[2, 4]));
342+
assert_eq!(a ^ b, RenderLayers::from_layers(&[1, 3, 5, 6]));
343+
344+
assert_eq!(RenderLayers::none() & MANY, RenderLayers::none());
345+
assert_eq!(RenderLayers::none() | MANY, MANY);
346+
assert_eq!(RenderLayers::none() ^ MANY, MANY);
347+
}
348+
349+
#[test]
350+
fn render_layer_shrink() {
351+
// Since it has layers greater than 64, the instance should take up two memory blocks
352+
let layers = RenderLayers::from_layers(&[1, 77]);
353+
assert!(layers.0.len() == 2);
354+
// When excluding that layer, it should drop the extra memory block
355+
let layers = layers.without(77);
356+
assert!(layers.0.len() == 1);
357+
}
245358
}

0 commit comments

Comments
 (0)