diff --git a/common/src/main/java/dev/ryanhcode/sable/physics/callback/FragileBlockCallback.java b/common/src/main/java/dev/ryanhcode/sable/physics/callback/FragileBlockCallback.java index 3cf124ef8..650e63922 100644 --- a/common/src/main/java/dev/ryanhcode/sable/physics/callback/FragileBlockCallback.java +++ b/common/src/main/java/dev/ryanhcode/sable/physics/callback/FragileBlockCallback.java @@ -52,6 +52,26 @@ public boolean shouldTriggerFor(final BlockState state) { } public CollisionResult onHit(final ServerLevel level, final BlockPos pos, final BlockState state, final Vector3d hitPos) { + if (SubLevelPhysicsSystem.IN_PHYSICS_STEP) { + // Breaking the block here would run its neighbour updates inside the native physics step, which can call back + // into Rapier (e.g. building a collider for a block state it hasn't seen yet) and hang the server thread. + // Queue the break until the step has finished instead. + SubLevelPhysicsSystem.getCurrentlySteppingSystem().queueFragileBlockBreak(pos, state); + } else { + breakFragileBlock(level, pos, state); + } + + return new CollisionResult(JOMLConversion.ZERO, true); + } + + /** + * Breaks a fragile block, melting ice on destruction + * + * @param level the level the block is in + * @param pos the position of the block + * @param state the state of the block being broken + */ + public static void breakFragileBlock(final ServerLevel level, final BlockPos pos, final BlockState state) { level.destroyBlock(pos, true); // Melt ice on destruction @@ -62,7 +82,5 @@ public CollisionResult onHit(final ServerLevel level, final BlockPos pos, final level.setBlockAndUpdate(pos, IceBlock.meltsInto()); } } - - return new CollisionResult(JOMLConversion.ZERO, true); } } diff --git a/common/src/main/java/dev/ryanhcode/sable/sublevel/system/SubLevelPhysicsSystem.java b/common/src/main/java/dev/ryanhcode/sable/sublevel/system/SubLevelPhysicsSystem.java index d4e68feef..a22d69b52 100644 --- a/common/src/main/java/dev/ryanhcode/sable/sublevel/system/SubLevelPhysicsSystem.java +++ b/common/src/main/java/dev/ryanhcode/sable/sublevel/system/SubLevelPhysicsSystem.java @@ -16,6 +16,7 @@ import dev.ryanhcode.sable.companion.math.Pose3d; import dev.ryanhcode.sable.mixinterface.plot.SubLevelContainerHolder; import dev.ryanhcode.sable.mixinterface.toast.SableToastableServer; +import dev.ryanhcode.sable.physics.callback.FragileBlockCallback; import dev.ryanhcode.sable.physics.config.PhysicsConfigData; import dev.ryanhcode.sable.physics.config.block_properties.PhysicsBlockPropertyHelper; import dev.ryanhcode.sable.physics.config.dimension_physics.DimensionPhysicsData; @@ -27,6 +28,8 @@ import dev.ryanhcode.sable.sublevel.plot.ServerLevelPlot; import dev.ryanhcode.sable.sublevel.storage.SubLevelRemovalReason; import dev.ryanhcode.sable.sublevel.system.ticket.PhysicsChunkTicketManager; +import it.unimi.dsi.fastutil.longs.Long2ObjectLinkedOpenHashMap; +import it.unimi.dsi.fastutil.longs.Long2ObjectMap; import it.unimi.dsi.fastutil.objects.Object2IntMap; import it.unimi.dsi.fastutil.objects.Object2IntOpenHashMap; import it.unimi.dsi.fastutil.objects.ObjectIterator; @@ -118,6 +121,11 @@ public class SubLevelPhysicsSystem implements SubLevelObserver { */ private final Collection queuedWakeUps = new ReferenceOpenHashSet<>(); + /** + * Fragile block breaks queued during the physics step, keyed by block position + */ + private final Long2ObjectLinkedOpenHashMap queuedFragileBlockBreaks = new Long2ObjectLinkedOpenHashMap<>(); + /** * Creates a new physics system. */ @@ -278,6 +286,7 @@ private void tickPipelinePhysics(final ServerSubLevelContainer container) { IN_PHYSICS_STEP = true; this.pipeline.physicsTick(substepTimeStep); IN_PHYSICS_STEP = false; + this.breakQueuedFragileBlocks(); // if any blocks were modified due to, say, fragile blocks breaking // sub-levels could have been removed during the physics tick @@ -514,6 +523,35 @@ public void wakeUpObjectsAt(final int x, final int y, final int z) { } } + /** + * Queues a fragile block to be broken once the current physics step has finished + * + * @param pos the position of the block + * @param state the state of the block when it was hit + */ + public void queueFragileBlockBreak(final BlockPos pos, final BlockState state) { + this.queuedFragileBlockBreaks.putIfAbsent(pos.asLong(), state); + } + + /** + * Breaks the fragile blocks queued during the physics step + */ + private void breakQueuedFragileBlocks() { + if (this.queuedFragileBlockBreaks.isEmpty()) + return; + + for (final Long2ObjectMap.Entry entry : this.queuedFragileBlockBreaks.long2ObjectEntrySet()) { + final BlockPos pos = BlockPos.of(entry.getLongKey()); + + // the block could have been changed since it was hit, e.g. by an earlier break + if (this.level.getBlockState(pos) == entry.getValue()) { + FragileBlockCallback.breakFragileBlock(this.level, pos, entry.getValue()); + } + } + + this.queuedFragileBlockBreaks.clear(); + } + public void updateMassDataFromBlockChange(final SubLevel subLevel, final BlockPos globalBlockPos, final BlockState oldState, final BlockState newState, final boolean notifyPipeline) { if ((subLevel instanceof final ServerSubLevel serverSubLevel)) {