Keep no face shapes from the load-time passes over the collision and support tables

analyse_shape and the cached support flags computed every shape's six faces
through face_shape, which caches them on the shape for good: about 39 MB that
nothing reads again. They now use face_shape_transient; runtime queries still
cache.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
Ludovic MarieandClaude Opus 5.5 committed 2026-10-30 08:04:59 +01:00
1 parent c48d6773c5
commit c8ee23f2b8
3 files changed
+62 -9

No files matched your search

@@ -16,6 +16,24 @@ impl VoxelShape {
.get_or_init(|| self.calculate_face(direction))
.clone()
}
/// [`Self::face_shape`] that keeps no face it had to calculate on this
/// shape: for a one-off pass over a whole table at load, whose faces
/// nothing asks for again (kept, they held tens of MB).
pub fn face_shape_transient(&self, direction: Direction) -> Result<Self, ShapeError> {
let (axis, _) = face_axis(direction);
if self.is_empty() || self.cube_like_along(axis) {
return Ok(self.clone());
}
match self
.identity
.faces
.get()
.and_then(|faces| faces[direction as usize].get())
{
Some(face) => face.clone(),
None => self.calculate_face(direction),
}
}
fn cube_like_along(&self, axis: Axis) -> bool {
let coords = self.coordinates(axis);
coords.len() == 2
@@ -120,4 +138,31 @@ mod tests {
assert!(weak.upgrade().is_none());
assert!(!face.is_empty());
}
/// A transient face is the cached one's geometry, keeps nothing, and
/// returns the cached face once there is one.
#[test]
fn transient_faces_are_not_kept() {
let shape = VoxelShape::from_cube(
[2, 2, 2],
&[true, false, false, false, false, false, false, false],
)
.unwrap();
for direction in Direction::ALL {
let kept = |shape: &VoxelShape| {
shape
.identity
.faces
.get()
.is_some_and(|faces| faces[direction as usize].get().is_some())
};
let transient = shape.face_shape_transient(direction).unwrap();
assert!(!kept(&shape), "{direction:?}");
let cached = shape.face_shape(direction).unwrap();
assert!(kept(&shape));
assert_eq!(transient.boxes(), cached.boxes(), "{direction:?}");
let again = shape.face_shape_transient(direction).unwrap();
assert!(again.same_identity(&cached));
}
}
}
+14 -7
View File
@@ -14,7 +14,10 @@ impl SupportType {
shape: &VoxelShape,
direction: Direction,
) -> Result<bool, ShapeError> {
let face = shape.face_shape(direction)?;
self.supported_by(&shape.face_shape(direction)?)
}
/// [`Self::is_supporting`] given the shape's face in that direction.
fn supported_by(self, face: &VoxelShape) -> Result<bool, ShapeError> {
let (required, operation) = match self {
Self::Full => (VoxelShape::block(), ShapeOp::XOR),
Self::Center => {
@@ -92,13 +95,17 @@ impl<'r> CachedBlockSupportRegistry<'r> {
*flags
} else {
let mut flags = [0; 3];
for (i, support) in [SupportType::Full, SupportType::Center, SupportType::Rigid]
.into_iter()
.enumerate()
{
for direction in Direction::ALL {
for direction in Direction::ALL {
// Not kept on the shape: only these flags are.
let face = shape
.face_shape_transient(direction)
.map_err(|e| invalid(e.to_string()))?;
for (i, support) in [SupportType::Full, SupportType::Center, SupportType::Rigid]
.into_iter()
.enumerate()
{
if support
.is_supporting(shape, direction)
.supported_by(&face)
.map_err(|e| invalid(e.to_string()))?
{
flags[i] |= 1 << direction as u8;
+3 -2
View File
@@ -116,8 +116,9 @@ pub(crate) fn analyse_shape(shape: &VoxelShape) -> io::Result<ShapeInfo> {
let err = |e: horizon_core::geometry::ShapeError| invalid(e.to_string());
let mut info = ShapeInfo::default();
let block = VoxelShape::block();
// The faces are not kept on the shape: only these facts are.
for direction in Direction::ALL {
let face = shape.face_shape(direction).map_err(err)?;
let face = shape.face_shape_transient(direction).map_err(err)?;
if !block.join_is_not_empty(&face, ShapeOp::XOR).map_err(err)? {
info.full_faces |= 1 << direction as u8;
}
@@ -125,7 +126,7 @@ pub(crate) fn analyse_shape(shape: &VoxelShape) -> io::Result<ShapeInfo> {
info.nonempty_faces |= 1 << direction as u8;
}
}
let above = shape.face_shape(Direction::Down).map_err(err)?;
let above = shape.face_shape_transient(Direction::Down).map_err(err)?;
let px = |v: f64| v / 16.0;
let tests = [
(7.0, 7.0, 9.0, 9.0),