Merge pull request #507 from ludovic111/perf/collision-scan-empty
Collision scans: skip blocks with no shape, build the entity shape lazily (−6 % tick time)
This commit is contained in:
@@ -209,21 +209,22 @@ where
|
||||
)
|
||||
.unwrap_or(false)
|
||||
}
|
||||
impl BlockCollisionWorld for SceneCollisions<'_, '_> {
|
||||
impl<'w> BlockCollisionWorld for SceneCollisions<'w, '_> {
|
||||
type Error = io::Error;
|
||||
type Chunk = ChunkPos;
|
||||
fn collision_chunk(&mut self, p: ChunkPos) -> io::Result<Option<ChunkPos>> {
|
||||
Ok(self.world.chunks.contains_key(&(p.x, p.z)).then_some(p))
|
||||
/// The resident column, looked up once per chunk the scan enters.
|
||||
type Chunk = &'w crate::chunk::Chunk;
|
||||
fn collision_chunk(&mut self, p: ChunkPos) -> io::Result<Option<Self::Chunk>> {
|
||||
Ok(self.world.chunks.get(&(p.x, p.z)).map(|chunk| &**chunk))
|
||||
}
|
||||
fn collision_block(&mut self, chunk: ChunkPos, p: BlockPos) -> io::Result<CollisionBlock> {
|
||||
let chunk = self
|
||||
.world
|
||||
.chunks
|
||||
.get(&(chunk.x, chunk.z))
|
||||
.ok_or_else(|| io::Error::other("resident collision chunk disappeared"))?;
|
||||
fn collision_block(&mut self, chunk: Self::Chunk, p: BlockPos) -> io::Result<CollisionBlock> {
|
||||
self.binding.collision_block(chunk.block(p.x, p.y, p.z))
|
||||
}
|
||||
fn is_suffocating(&mut self, _: ChunkPos, _: CollisionBlock, _: BlockPos) -> io::Result<bool> {
|
||||
fn is_suffocating(
|
||||
&mut self,
|
||||
_: Self::Chunk,
|
||||
_: CollisionBlock,
|
||||
_: BlockPos,
|
||||
) -> io::Result<bool> {
|
||||
Ok(false)
|
||||
}
|
||||
fn context_shape(
|
||||
|
||||
@@ -192,6 +192,7 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
state: BlockStateId(i as u32),
|
||||
large_collision_shape: v[1].as_bool().unwrap(),
|
||||
moving_piston: v[2].as_bool().unwrap(),
|
||||
empty_shape: false,
|
||||
},
|
||||
shape: shape(v[0].as_u64().unwrap()),
|
||||
suffocating: v[3].as_bool().unwrap(),
|
||||
|
||||
@@ -98,8 +98,6 @@ impl<'r> BlockCollisionRegistry<'r> {
|
||||
.into_iter()
|
||||
.any(|axis| shape.min(axis) < 0.0 || shape.max(axis) > 1.0)
|
||||
});
|
||||
scan_flags
|
||||
.push(u8::from(large) | (u8::from(state.name == "minecraft:moving_piston") << 1));
|
||||
if let Some(rule) = ScaffoldingCollision::from_state(state)? {
|
||||
contextual.insert(id, ContextualRule::Scaffolding(rule));
|
||||
} else if let Some(rule) = LiquidCollision::from_state(state)? {
|
||||
@@ -107,6 +105,14 @@ impl<'r> BlockCollisionRegistry<'r> {
|
||||
} else if state.name == "minecraft:powder_snow" {
|
||||
contextual.insert(id, ContextualRule::PowderSnow);
|
||||
}
|
||||
// No contextual rule and an empty cached shape: empty everywhere.
|
||||
let empty = !contextual.contains_key(&id)
|
||||
&& cached.cached_shape(id)?.is_some_and(VoxelShape::is_empty);
|
||||
scan_flags.push(
|
||||
u8::from(large)
|
||||
| (u8::from(state.name == "minecraft:moving_piston") << 1)
|
||||
| (u8::from(empty) << 2),
|
||||
);
|
||||
if cached.cached_shape(id)?.is_none() {
|
||||
if let Some(rule) =
|
||||
DynamicSupport::from_state(cached.registry().state(id).unwrap())?
|
||||
@@ -138,6 +144,7 @@ impl<'r> BlockCollisionRegistry<'r> {
|
||||
state,
|
||||
large_collision_shape: flags & 1 != 0,
|
||||
moving_piston: flags & 2 != 0,
|
||||
empty_shape: flags & 4 != 0,
|
||||
})
|
||||
}
|
||||
pub fn collision_shape<W: DynamicCollisionWorld>(
|
||||
|
||||
@@ -11,6 +11,10 @@ pub struct CollisionBlock {
|
||||
pub state: BlockStateId,
|
||||
pub large_collision_shape: bool,
|
||||
pub moving_piston: bool,
|
||||
/// The shape is empty under every context (air, plants): a scan skips
|
||||
/// the block without asking for its shape. Providers whose shapes come
|
||||
/// from elsewhere leave it false.
|
||||
pub empty_shape: bool,
|
||||
}
|
||||
|
||||
pub trait BlockCollisionWorld {
|
||||
@@ -72,7 +76,9 @@ pub struct BlockCollisionScan<'w, 'c, W: BlockCollisionWorld> {
|
||||
world: &'w mut W,
|
||||
context: CollisionContext<'c>,
|
||||
bounds: Aabb,
|
||||
entity_shape: VoxelShape,
|
||||
/// `Shapes.create(box)`, built the first time a block's shape is not
|
||||
/// the unit cube (finite bounds always make one).
|
||||
entity_shape: Option<VoxelShape>,
|
||||
only_suffocating: bool,
|
||||
start: BlockPos,
|
||||
sizes: [usize; 3],
|
||||
@@ -120,7 +126,7 @@ impl<'w, 'c, W: BlockCollisionWorld> BlockCollisionScan<'w, 'c, W> {
|
||||
world,
|
||||
context,
|
||||
bounds,
|
||||
entity_shape: VoxelShape::from_box(bounds).map_err(CollisionScanError::Shape)?,
|
||||
entity_shape: None,
|
||||
only_suffocating,
|
||||
start,
|
||||
sizes: sizes.map(|s| s as usize),
|
||||
@@ -185,6 +191,9 @@ impl<'w, 'c, W: BlockCollisionWorld> BlockCollisionScan<'w, 'c, W> {
|
||||
if kind == 1 && !block.large_collision_shape || kind == 2 && !block.moving_piston {
|
||||
continue;
|
||||
}
|
||||
if block.empty_shape {
|
||||
continue;
|
||||
}
|
||||
let shape = self
|
||||
.world
|
||||
.context_shape(block, p, &self.context)
|
||||
@@ -201,10 +210,24 @@ impl<'w, 'c, W: BlockCollisionWorld> BlockCollisionScan<'w, 'c, W> {
|
||||
}
|
||||
shape.moved(offset)
|
||||
} else {
|
||||
// Shapes whose bounds miss the box share no volume with it:
|
||||
// joinIsNotEmpty(AND) is false without moving them.
|
||||
if !shape
|
||||
.bounds()
|
||||
.is_some_and(|b| b.move_by(offset).intersects(self.bounds))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
let moved = shape.moved(offset);
|
||||
let entity_shape = match &self.entity_shape {
|
||||
Some(shape) => shape,
|
||||
None => self.entity_shape.insert(
|
||||
VoxelShape::from_box(self.bounds).map_err(CollisionScanError::Shape)?,
|
||||
),
|
||||
};
|
||||
if moved.is_empty()
|
||||
|| !moved
|
||||
.join_is_not_empty(&self.entity_shape, ShapeOp::AND)
|
||||
.join_is_not_empty(entity_shape, ShapeOp::AND)
|
||||
.map_err(CollisionScanError::Shape)?
|
||||
{
|
||||
continue;
|
||||
|
||||
Reference in New Issue
Block a user