From b88b834e0b42ac317c05fea9d26923fdb6613c0f Mon Sep 17 00:00:00 2001 From: mynameisgrass Date: Sun, 26 Jul 2026 10:15:29 +0700 Subject: [PATCH 1/2] ssarg fix the ae2 conflict and the raper3d getpose non-unwinding panic --- gradle.properties | 4 +- .../impl/rapier/RapierPhysicsPipeline.java | 136 ++++++++++-- .../src/main/rust/rapier/src/boxes.rs | 22 +- .../src/main/rust/rapier/src/joints.rs | 73 ++++--- sable_rapier/src/main/rust/rapier/src/lib.rs | 204 ++++++++---------- 5 files changed, 261 insertions(+), 178 deletions(-) diff --git a/gradle.properties b/gradle.properties index d7f3cf59..de63da27 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,4 +1,4 @@ -version=2.0.3 +version=2.0.3-ssargfix-0.2 group=dev.ryanhcode.sable java_version=21 @@ -7,7 +7,7 @@ minecraft_version=1.21.1 mod_name=Sable mod_author=RyanHCode mod_id=sable -credits=Ocelot, Eriksonn, Cyvack, Bee, Kyan, Cake, Rhyguy1 +credits=Ocelot, Eriksonn, Cyvack, Bee, Kyan, Cake, Rhyguy1, ssarg license=PolyForm Shield License 1.0.0 description=Interactive moving block structures with physics. issues=https://github.com/ryanhcode/sable/issues diff --git a/sable_rapier/src/main/java/dev/ryanhcode/sable/physics/impl/rapier/RapierPhysicsPipeline.java b/sable_rapier/src/main/java/dev/ryanhcode/sable/physics/impl/rapier/RapierPhysicsPipeline.java index f741a1a1..2bb12f8e 100644 --- a/sable_rapier/src/main/java/dev/ryanhcode/sable/physics/impl/rapier/RapierPhysicsPipeline.java +++ b/sable_rapier/src/main/java/dev/ryanhcode/sable/physics/impl/rapier/RapierPhysicsPipeline.java @@ -118,7 +118,9 @@ protected long getSceneHandle() { @Override public void init(@Nullable final Vector3dc gravity, final double universalDrag) { try { + Sable.LOGGER.info("[SableDebug] Initializing Rapier physics pipeline with gravity=({}, {}, {}), drag={}", gravity.x(), gravity.y(), gravity.z(), universalDrag); this.scene = new RapierPhysicsScene(Rapier3D.initialize(gravity.x(), gravity.y(), gravity.z(), universalDrag)); + Sable.LOGGER.info("[SableDebug] Rapier physics pipeline initialized successfully, scene handle={}", this.scene.handle()); } catch (final UnsatisfiedLinkError e) { Sable.LOGGER.error("Sable has failed to link with the natives for its Rapier pipeline. Please report with system details to " + Sable.ISSUE_TRACKER_URL, e); final CrashReport crashReport = CrashReport.forThrowable(e.getCause(), "Sable linking with Rapier natives"); @@ -145,7 +147,13 @@ public void dispose() { @Override public void prePhysicsTicks() { final double timeStep = 1.0 / 20.0; - Rapier3D.tick(this.scene.handle(), timeStep); + try { + Sable.LOGGER.debug("[SableDebug] prePhysicsTicks: calling tick(handle={}, timeStep={})", this.scene.handle(), timeStep); + Rapier3D.tick(this.scene.handle(), timeStep); + } catch (final Exception e) { + Sable.LOGGER.error("[SableDebug] EXCEPTION in prePhysicsTicks tick() call", e); + throw e; + } } /** @@ -156,14 +164,26 @@ public void prePhysicsTicks() { @Override public void physicsTick(final double timeStep) { this.updateContraptionPoses(); - Rapier3D.step(this.scene.handle(), timeStep); + try { + Sable.LOGGER.debug("[SableDebug] physicsTick: calling step(handle={}, timeStep={})", this.scene.handle(), timeStep); + Rapier3D.step(this.scene.handle(), timeStep); + } catch (final Exception e) { + Sable.LOGGER.error("[SableDebug] EXCEPTION in physicsTick step() call", e); + throw e; + } for (final PhysicsPipelineBody queuedWakeUp : this.queuedWakeUps) { if (queuedWakeUp.isRemoved()) { continue; } - Rapier3D.wakeUpObject(this.scene.handle(), queuedWakeUp.getRuntimeId()); + final int wakeId = Rapier3D.getID(queuedWakeUp); + Sable.LOGGER.debug("[SableDebug] physicsTick: waking up body id={}", wakeId); + try { + Rapier3D.wakeUpObject(this.scene.handle(), wakeId); + } catch (final Exception e) { + Sable.LOGGER.error("[SableDebug] EXCEPTION waking up body id={}", wakeId, e); + } } this.queuedWakeUps.clear(); @@ -195,18 +215,25 @@ public void add(final ServerSubLevel subLevel, final Pose3dc pose) { final Quaterniondc rot = pose.orientation(); final int id = Rapier3D.getID(subLevel); - Rapier3D.createSubLevel(this.scene.handle(), id, new double[]{pos.x(), pos.y(), pos.z(), rot.x(), rot.y(), rot.z(), rot.w()}); + Sable.LOGGER.info("[SableDebug] add(SubLevel): id={}, pos=({}, {}, {}), rot=({}, {}, {}, {})", id, pos.x(), pos.y(), pos.z(), rot.x(), rot.y(), rot.z(), rot.w()); + try { + Rapier3D.createSubLevel(this.scene.handle(), id, new double[]{pos.x(), pos.y(), pos.z(), rot.x(), rot.y(), rot.z(), rot.w()}); + } catch (final Exception e) { + Sable.LOGGER.error("[SableDebug] EXCEPTION in add(SubLevel) createSubLevel, id={}", id, e); + throw e; + } subLevel.updateMergedMassData(1.0f); final Vector3dc centerOfMass = subLevel.getMassTracker().getCenterOfMass(); if (centerOfMass != null) { subLevel.logicalPose().rotationPoint().set(centerOfMass); - + Sable.LOGGER.info("[SableDebug] add(SubLevel): id={}, centerOfMass=({}, {}, {})", id, centerOfMass.x(), centerOfMass.y(), centerOfMass.z()); this.onStatsChanged(subLevel); } this.activeSubLevels.put(Rapier3D.getID(subLevel), subLevel); + Sable.LOGGER.info("[SableDebug] add(SubLevel): id={} added successfully, activeSubLevels.size={}", id, this.activeSubLevels.size()); } /** @@ -214,8 +241,16 @@ public void add(final ServerSubLevel subLevel, final Pose3dc pose) { */ @Override public void remove(final ServerSubLevel subLevel) { - Rapier3D.removeSubLevel(this.scene.handle(), Rapier3D.getID(subLevel)); - this.activeSubLevels.remove(Rapier3D.getID(subLevel)); + final int id = Rapier3D.getID(subLevel); + Sable.LOGGER.info("[SableDebug] remove(SubLevel): id={}", id); + try { + Rapier3D.removeSubLevel(this.scene.handle(), id); + } catch (final Exception e) { + Sable.LOGGER.error("[SableDebug] EXCEPTION in remove(SubLevel), id={}", id, e); + throw e; + } + this.activeSubLevels.remove(id); + Sable.LOGGER.info("[SableDebug] remove(SubLevel): id={} removed, activeSubLevels.size={}", id, this.activeSubLevels.size()); } /** @@ -301,11 +336,19 @@ public void remove(final KinematicContraption contraption) { @Override public Pose3d readPose(final ServerSubLevel subLevel, final Pose3d dest) { this.assertBodyValid(subLevel); - Rapier3D.getPose(this.scene.handle(), Rapier3D.getID(subLevel), this.poseCache); + final int id = Rapier3D.getID(subLevel); + Sable.LOGGER.debug("[SableDebug] readPose: id={}", id); + try { + Rapier3D.getPose(this.scene.handle(), id, this.poseCache); + } catch (final Exception e) { + Sable.LOGGER.error("[SableDebug] EXCEPTION in readPose getPose(), id={}", id, e); + throw e; + } dest.position().set(this.poseCache[0], this.poseCache[1], this.poseCache[2]); dest.orientation().set(this.poseCache[3], this.poseCache[4], this.poseCache[5], this.poseCache[6]); + Sable.LOGGER.debug("[SableDebug] readPose: id={}, pos=({}, {}, {}), rot=({}, {}, {}, {})", id, this.poseCache[0], this.poseCache[1], this.poseCache[2], this.poseCache[3], this.poseCache[4], this.poseCache[5], this.poseCache[6]); return dest; } @@ -331,6 +374,7 @@ public BoxHandle addBox(final BoxPhysicsObject box) { @Override public void handleChunkSectionAddition(final LevelChunkSection section, final int x, final int y, final int z, final boolean uploadDataIfGlobal) { this.accelerator.clearCache(); + Sable.LOGGER.debug("[SableDebug] handleChunkSectionAddition: section=({}, {}, {}), uploadDataIfGlobal={}, hasOnlyAir={}", x, y, z, uploadDataIfGlobal, section.hasOnlyAir()); // this means the x coordinate is the fastest changing, then z, then y final int[] array = new int[LevelChunkSection.SECTION_SIZE]; @@ -362,7 +406,12 @@ public void handleChunkSectionAddition(final LevelChunkSection section, final in int id = -1; if (plot != null && uploadDataIfGlobal) id = Rapier3D.getID(((ServerSubLevel) plot.getSubLevel())); - Rapier3D.addChunk(this.scene.handle(), x, y, z, array, global, id); + try { + Rapier3D.addChunk(this.scene.handle(), x, y, z, array, global, id); + } catch (final Exception e) { + Sable.LOGGER.error("[SableDebug] EXCEPTION in handleChunkSectionAddition addChunk({}, {}, {}), global={}, id={}", x, y, z, global, id, e); + throw e; + } } /** @@ -370,7 +419,13 @@ public void handleChunkSectionAddition(final LevelChunkSection section, final in */ @Override public void handleChunkSectionRemoval(final int x, final int y, final int z) { - Rapier3D.removeChunk(this.scene.handle(), x, y, z, !SubLevelContainer.getContainer(this.level).inBounds(x, z)); + Sable.LOGGER.debug("[SableDebug] handleChunkSectionRemoval: ({}, {}, {})", x, y, z); + try { + Rapier3D.removeChunk(this.scene.handle(), x, y, z, !SubLevelContainer.getContainer(this.level).inBounds(x, z)); + } catch (final Exception e) { + Sable.LOGGER.error("[SableDebug] EXCEPTION in handleChunkSectionRemoval removeChunk({}, {}, {})", x, y, z, e); + throw e; + } } /** @@ -387,6 +442,8 @@ public void handleBlockChange(final SectionPos sectionPos, final LevelChunkSecti y = (sectionPos.y() << 4) + y; z = (sectionPos.z() << 4) + z; + Sable.LOGGER.debug("[SableDebug] handleBlockChange: pos=({}, {}, {}), oldState={}, newState={}", x, y, z, oldState, newState); + final BlockPos globalBlockPos = new BlockPos(x, y, z); for (final Direction dir : Direction.values()) { @@ -395,7 +452,12 @@ public void handleBlockChange(final SectionPos sectionPos, final LevelChunkSecti final RapierVoxelColliderData colliderData = this.colliderBakery.getPhysicsDataForBlock(this.level.getBlockState(pos)); final int colliderValue = colliderData == null ? 0 : colliderData.handle() + 1; - Rapier3D.changeBlock(this.scene.handle(), pos.getX(), pos.getY(), pos.getZ(), packBlockState(state, colliderValue)); + try { + Rapier3D.changeBlock(this.scene.handle(), pos.getX(), pos.getY(), pos.getZ(), packBlockState(state, colliderValue)); + } catch (final Exception e) { + Sable.LOGGER.error("[SableDebug] EXCEPTION in handleBlockChange changeBlock neighbor ({}, {}, {}), colliderValue={}", pos.getX(), pos.getY(), pos.getZ(), colliderValue, e); + throw e; + } } // do it for the block without offset @@ -403,7 +465,12 @@ public void handleBlockChange(final SectionPos sectionPos, final LevelChunkSecti final RapierVoxelColliderData colliderData = this.colliderBakery.getPhysicsDataForBlock(newState); final int colliderValue = colliderData == null ? 0 : colliderData.handle() + 1; - Rapier3D.changeBlock(this.scene.handle(), x, y, z, packBlockState(state, colliderValue)); + try { + Rapier3D.changeBlock(this.scene.handle(), x, y, z, packBlockState(state, colliderValue)); + } catch (final Exception e) { + Sable.LOGGER.error("[SableDebug] EXCEPTION in handleBlockChange changeBlock self ({}, {}, {}), colliderValue={}", x, y, z, colliderValue, e); + throw e; + } } @Override @@ -415,11 +482,23 @@ public void onStatsChanged(@NotNull final ServerSubLevel subLevel) { final Vector3dc centerOfMass = subLevel.getMassTracker().getCenterOfMass(); if (centerOfMass != null) { - Rapier3D.setCenterOfMass(this.scene.handle(), id, centerOfMass.x(), centerOfMass.y(), centerOfMass.z()); - Rapier3D.setMassPropertiesFrom(this.scene.handle(), id, subLevel.getMassTracker()); + Sable.LOGGER.debug("[SableDebug] onStatsChanged: id={}, centerOfMass=({}, {}, {}), mass={}", id, centerOfMass.x(), centerOfMass.y(), centerOfMass.z(), subLevel.getMassTracker().getMass()); + try { + Rapier3D.setCenterOfMass(this.scene.handle(), id, centerOfMass.x(), centerOfMass.y(), centerOfMass.z()); + Rapier3D.setMassPropertiesFrom(this.scene.handle(), id, subLevel.getMassTracker()); + } catch (final Exception e) { + Sable.LOGGER.error("[SableDebug] EXCEPTION in onStatsChanged setCenterOfMass/setMassProperties, id={}", id, e); + throw e; + } } - Rapier3D.setLocalBounds(this.scene.handle(), id, plotBounds.minX(), plotBounds.minY(), plotBounds.minZ(), plotBounds.maxX(), plotBounds.maxY(), plotBounds.maxZ()); + Sable.LOGGER.debug("[SableDebug] onStatsChanged: id={}, bounds=({},{},{} -> {},{},{})", id, plotBounds.minX(), plotBounds.minY(), plotBounds.minZ(), plotBounds.maxX(), plotBounds.maxY(), plotBounds.maxZ()); + try { + Rapier3D.setLocalBounds(this.scene.handle(), id, plotBounds.minX(), plotBounds.minY(), plotBounds.minZ(), plotBounds.maxX(), plotBounds.maxY(), plotBounds.maxZ()); + } catch (final Exception e) { + Sable.LOGGER.error("[SableDebug] EXCEPTION in onStatsChanged setLocalBounds, id={}", id, e); + throw e; + } } /** @@ -433,7 +512,14 @@ public void onStatsChanged(@NotNull final ServerSubLevel subLevel) { public void teleport(final PhysicsPipelineBody body, final Vector3dc position, final Quaterniondc orientation) { this.assertBodyValid(body); - Rapier3D.teleportObject(this.scene.handle(), Rapier3D.getID(body), position.x(), position.y(), position.z(), orientation.x(), orientation.y(), orientation.z(), orientation.w()); + final int id = Rapier3D.getID(body); + Sable.LOGGER.debug("[SableDebug] teleport: id={}, pos=({}, {}, {}), rot=({}, {}, {}, {})", id, position.x(), position.y(), position.z(), orientation.x(), orientation.y(), orientation.z(), orientation.w()); + try { + Rapier3D.teleportObject(this.scene.handle(), id, position.x(), position.y(), position.z(), orientation.x(), orientation.y(), orientation.z(), orientation.w()); + } catch (final Exception e) { + Sable.LOGGER.error("[SableDebug] EXCEPTION in teleport, id={}", id, e); + throw e; + } if (body instanceof final ServerSubLevel subLevel) { subLevel.logicalPose().position().set(position); subLevel.logicalPose().orientation().set(orientation); @@ -451,8 +537,15 @@ public void teleport(final PhysicsPipelineBody body, final Vector3dc position, f public void applyImpulse(final PhysicsPipelineBody body, final Vector3dc position, final Vector3dc force) { this.assertBodyValid(body); + final int id = Rapier3D.getID(body); final Vector3dc centerOfMass = body.getMassTracker().getCenterOfMass(); - Rapier3D.applyForce(this.scene.handle(), Rapier3D.getID(body), position.x() - centerOfMass.x(), position.y() - centerOfMass.y(), position.z() - centerOfMass.z(), force.x(), force.y(), force.z(), true); + Sable.LOGGER.debug("[SableDebug] applyImpulse: id={}, relPos=({}, {}, {}), force=({}, {}, {})", id, position.x() - centerOfMass.x(), position.y() - centerOfMass.y(), position.z() - centerOfMass.z(), force.x(), force.y(), force.z()); + try { + Rapier3D.applyForce(this.scene.handle(), id, position.x() - centerOfMass.x(), position.y() - centerOfMass.y(), position.z() - centerOfMass.z(), force.x(), force.y(), force.z(), true); + } catch (final Exception e) { + Sable.LOGGER.error("[SableDebug] EXCEPTION in applyImpulse, id={}", id, e); + throw e; + } } /** @@ -464,7 +557,14 @@ public void applyImpulse(final PhysicsPipelineBody body, final Vector3dc positio @Override public void applyLinearAndAngularImpulse(final PhysicsPipelineBody body, final Vector3dc force, final Vector3dc torque, final boolean wakeUp) { this.assertBodyValid(body); - Rapier3D.applyForceAndTorque(this.scene.handle(), Rapier3D.getID(body), force.x(), force.y(), force.z(), torque.x(), torque.y(), torque.z(), wakeUp); + final int id = Rapier3D.getID(body); + Sable.LOGGER.debug("[SableDebug] applyLinearAndAngularImpulse: id={}, force=({}, {}, {}), torque=({}, {}, {}), wakeUp={}", id, force.x(), force.y(), force.z(), torque.x(), torque.y(), torque.z(), wakeUp); + try { + Rapier3D.applyForceAndTorque(this.scene.handle(), id, force.x(), force.y(), force.z(), torque.x(), torque.y(), torque.z(), wakeUp); + } catch (final Exception e) { + Sable.LOGGER.error("[SableDebug] EXCEPTION in applyLinearAndAngularImpulse, id={}", id, e); + throw e; + } } /** diff --git a/sable_rapier/src/main/rust/rapier/src/boxes.rs b/sable_rapier/src/main/rust/rapier/src/boxes.rs index faf5a3ed..3479db38 100644 --- a/sable_rapier/src/main/rust/rapier/src/boxes.rs +++ b/sable_rapier/src/main/rust/rapier/src/boxes.rs @@ -81,16 +81,16 @@ pub extern "system" fn Java_dev_ryanhcode_sable_physics_impl_rapier_Rapier3D_rem let sim_data = &mut *sim_data; let mut sable_data = scene.sable_data.write().unwrap(); - let handle = sable_data.rigid_bodies[&(id as LevelColliderID)]; - sim_data.rigid_body_set.remove( - handle, - &mut sim_data.island_manager, - &mut sim_data.collider_set, - &mut sim_data.impulse_joint_set, - &mut sim_data.multibody_joint_set, - true, - ); - - sable_data.rigid_bodies.remove(&(id as LevelColliderID)); + if let Some(&handle) = sable_data.rigid_bodies.get(&(id as LevelColliderID)) { + sim_data.rigid_body_set.remove( + handle, + &mut sim_data.island_manager, + &mut sim_data.collider_set, + &mut sim_data.impulse_joint_set, + &mut sim_data.multibody_joint_set, + true, + ); + sable_data.rigid_bodies.remove(&(id as LevelColliderID)); + } }) } diff --git a/sable_rapier/src/main/rust/rapier/src/joints.rs b/sable_rapier/src/main/rust/rapier/src/joints.rs index 4d99b288..9b144ee3 100644 --- a/sable_rapier/src/main/rust/rapier/src/joints.rs +++ b/sable_rapier/src/main/rust/rapier/src/joints.rs @@ -1,5 +1,5 @@ use crate::config::{JOINT_SPRING_DAMPING_RATIO, JOINT_SPRING_FREQUENCY}; -use crate::scene::{LevelColliderID, PhysicsScene}; +use crate::scene::{LevelColliderID, PhysicsScene, SableSceneData}; use crate::with_handle; use jni::JNIEnv; use jni::objects::{JClass, JDoubleArray}; @@ -10,12 +10,25 @@ use rapier3d::dynamics::{ }; use rapier3d::glamx::{DVec3, Quat}; use rapier3d::math::Vec3; -use rapier3d::prelude::{FixedJointBuilder, ImpulseJointHandle}; +use rapier3d::prelude::{FixedJointBuilder, ImpulseJointHandle, RigidBodyHandle}; use std::collections::HashMap; type SableJointHandle = jlong; type RapierJointHandle = ImpulseJointHandle; +fn get_rb( + sable_data: &SableSceneData, + ground_handle: RigidBodyHandle, + id: jint, +) -> Option { + if id == -1 { + Some(ground_handle) + } else { + sable_data.rigid_bodies.get(&(id as LevelColliderID)).copied() + } +} + + struct SubLevelJoint { id_a: Option, id_b: Option, @@ -326,16 +339,14 @@ pub extern "system" fn Java_dev_ryanhcode_sable_physics_impl_rapier_Rapier3D_add let mut sable_data = scene.sable_data.write().unwrap(); let mut sim_data = scene.sim_data.write().unwrap(); - let rb_a = if id_a == -1 { - scene.ground_handle.unwrap() - } else { - sable_data.rigid_bodies[&(id_a as LevelColliderID)] + let rb_a = match get_rb(&sable_data, scene.ground_handle.unwrap(), id_a) { + Some(rb) => rb, + None => return -1, }; - let rb_b = if id_b == -1 { - scene.ground_handle.unwrap() - } else { - sable_data.rigid_bodies[&(id_b as LevelColliderID)] + let rb_b = match get_rb(&sable_data, scene.ground_handle.unwrap(), id_b) { + Some(rb) => rb, + None => return -1, }; let revolute = RevoluteJointBuilder::new( @@ -413,16 +424,14 @@ pub extern "system" fn Java_dev_ryanhcode_sable_physics_impl_rapier_Rapier3D_add let mut sable_data = scene.sable_data.write().unwrap(); let mut sim_data = scene.sim_data.write().unwrap(); - let rb_a = if id_a == -1 { - scene.ground_handle.unwrap() - } else { - sable_data.rigid_bodies[&(id_a as LevelColliderID)] + let rb_a = match get_rb(&sable_data, scene.ground_handle.unwrap(), id_a) { + Some(rb) => rb, + None => return -1, }; - let rb_b = if id_b == -1 { - scene.ground_handle.unwrap() - } else { - sable_data.rigid_bodies[&(id_b as LevelColliderID)] + let rb_b = match get_rb(&sable_data, scene.ground_handle.unwrap(), id_b) { + Some(rb) => rb, + None => return -1, }; let quat = Quat::from_xyzw( @@ -505,16 +514,14 @@ pub extern "system" fn Java_dev_ryanhcode_sable_physics_impl_rapier_Rapier3D_add let mut sable_data = scene.sable_data.write().unwrap(); let mut sim_data = scene.sim_data.write().unwrap(); - let rb_a = if id_a == -1 { - scene.ground_handle.unwrap() - } else { - sable_data.rigid_bodies[&(id_a as LevelColliderID)] + let rb_a = match get_rb(&sable_data, scene.ground_handle.unwrap(), id_a) { + Some(rb) => rb, + None => return -1, }; - let rb_b = if id_b == -1 { - scene.ground_handle.unwrap() - } else { - sable_data.rigid_bodies[&(id_b as LevelColliderID)] + let rb_b = match get_rb(&sable_data, scene.ground_handle.unwrap(), id_b) { + Some(rb) => rb, + None => return -1, }; let mut joint = GenericJointBuilder::new(JointAxesMask::empty()).softness( @@ -599,16 +606,14 @@ pub extern "system" fn Java_dev_ryanhcode_sable_physics_impl_rapier_Rapier3D_add let mut sable_data = scene.sable_data.write().unwrap(); let mut sim_data = scene.sim_data.write().unwrap(); - let rb_a = if id_a == -1 { - scene.ground_handle.unwrap() - } else { - sable_data.rigid_bodies[&(id_a as LevelColliderID)] + let rb_a = match get_rb(&sable_data, scene.ground_handle.unwrap(), id_a) { + Some(rb) => rb, + None => return -1, }; - let rb_b = if id_b == -1 { - scene.ground_handle.unwrap() - } else { - sable_data.rigid_bodies[&(id_b as LevelColliderID)] + let rb_b = match get_rb(&sable_data, scene.ground_handle.unwrap(), id_b) { + Some(rb) => rb, + None => return -1, }; let locked_axes = JointAxesMask::from_bits_truncate(locked_axes_mask as u8); diff --git a/sable_rapier/src/main/rust/rapier/src/lib.rs b/sable_rapier/src/main/rust/rapier/src/lib.rs index 2d224733..bca60aef 100644 --- a/sable_rapier/src/main/rust/rapier/src/lib.rs +++ b/sable_rapier/src/main/rust/rapier/src/lib.rs @@ -282,12 +282,9 @@ pub fn get_rigid_body_mut<'a>( sim: &'a mut SimulationSceneData, sable_data: &SableSceneData, id: LevelColliderID, -) -> &'a mut RigidBody { - let handle = sable_data - .rigid_bodies - .get(&id) - .expect("No rigid body for id"); - &mut sim.rigid_body_set[*handle] +) -> Option<&'a mut RigidBody> { + let handle = sable_data.rigid_bodies.get(&id)?; + sim.rigid_body_set.get_mut(*handle) } #[inline(always)] @@ -295,12 +292,9 @@ pub fn get_rigid_body<'a>( sim: &'a SimulationSceneData, sable_data: &SableSceneData, id: LevelColliderID, -) -> &'a RigidBody { - let handle = sable_data - .rigid_bodies - .get(&id) - .expect("No rigid body for id"); - &sim.rigid_body_set[*handle] +) -> Option<&'a RigidBody> { + let handle = sable_data.rigid_bodies.get(&id)?; + sim.rigid_body_set.get(*handle) } #[unsafe(no_mangle)] @@ -527,20 +521,19 @@ pub extern "system" fn Java_dev_ryanhcode_sable_physics_impl_rapier_Rapier3D_get let sable_data = scene.sable_data.read().unwrap(); let sim_data = scene.sim_data.read().unwrap(); - let rb: &RigidBody = - &sim_data.rigid_body_set[sable_data.rigid_bodies[&(id as LevelColliderID)]]; - - let arr: [jdouble; 7] = [ - rb.translation().x as jdouble, - rb.translation().y as jdouble, - rb.translation().z as jdouble, - rb.rotation().x as jdouble, - rb.rotation().y as jdouble, - rb.rotation().z as jdouble, - rb.rotation().w as jdouble, - ]; - - env.set_double_array_region(&store, 0, &arr).unwrap(); + if let Some(rb) = get_rigid_body(&sim_data, &sable_data, id as LevelColliderID) { + let arr: [jdouble; 7] = [ + rb.translation().x as jdouble, + rb.translation().y as jdouble, + rb.translation().z as jdouble, + rb.rotation().x as jdouble, + rb.rotation().y as jdouble, + rb.rotation().z as jdouble, + rb.rotation().w as jdouble, + ]; + + let _ = env.set_double_array_region(&store, 0, &arr); + } }) } @@ -1112,12 +1105,12 @@ pub extern "system" fn Java_dev_ryanhcode_sable_physics_impl_rapier_Rapier3D_set let sable_data = scene.sable_data.read().unwrap(); let mut sim_data = scene.sim_data.write().unwrap(); - let rb = &mut sim_data.rigid_body_set[sable_data.rigid_bodies[&(id as LevelColliderID)]]; - - rb.set_additional_mass_properties( - MassProperties::with_inertia_matrix(Vec3::ZERO, mass as Real, inertia_tensor.into()), - true, - ); + if let Some(rb) = get_rigid_body_mut(&mut sim_data, &sable_data, id as LevelColliderID) { + rb.set_additional_mass_properties( + MassProperties::with_inertia_matrix(Vec3::ZERO, mass as Real, inertia_tensor.into()), + true, + ); + } }) } @@ -1142,12 +1135,12 @@ pub extern "system" fn Java_dev_ryanhcode_sable_physics_impl_rapier_Rapier3D_tel let sable_data = scene.sable_data.read().unwrap(); let mut sim_data = scene.sim_data.write().unwrap(); - let rb = &mut sim_data.rigid_body_set[sable_data.rigid_bodies[&(id as LevelColliderID)]]; - - let mut pose = *rb.position(); - pose.translation = Vec3::new(x as Real, y as Real, z as Real); - pose.rotation = Quat::from_xyzw(i as Real, j as Real, k as Real, r as Real); - rb.set_position(pose, true); + if let Some(rb) = get_rigid_body_mut(&mut sim_data, &sable_data, id as LevelColliderID) { + let mut pose = *rb.position(); + pose.translation = Vec3::new(x as Real, y as Real, z as Real); + pose.rotation = Quat::from_xyzw(i as Real, j as Real, k as Real, r as Real); + rb.set_position(pose, true); + } }) } @@ -1164,8 +1157,9 @@ pub extern "system" fn Java_dev_ryanhcode_sable_physics_impl_rapier_Rapier3D_wak with_handle(handle, |scene| { let sable_data = scene.sable_data.read().unwrap(); let mut sim_data = scene.sim_data.write().unwrap(); - let rb = &mut sim_data.rigid_body_set[sable_data.rigid_bodies[&(id as LevelColliderID)]]; - rb.wake_up(true); + if let Some(rb) = get_rigid_body_mut(&mut sim_data, &sable_data, id as LevelColliderID) { + rb.wake_up(true); + } }) } @@ -1188,20 +1182,20 @@ pub extern "system" fn Java_dev_ryanhcode_sable_physics_impl_rapier_Rapier3D_add with_handle(handle, |scene| { let sable_data = scene.sable_data.read().unwrap(); let mut sim_data = scene.sim_data.write().unwrap(); - let rb = get_rigid_body_mut(&mut sim_data, &sable_data, id as LevelColliderID); + if let Some(rb) = get_rigid_body_mut(&mut sim_data, &sable_data, id as LevelColliderID) { + if wake_up == 0 && rb.is_sleeping() { + return; + } - if wake_up == 0 && rb.is_sleeping() { - return; + rb.set_linvel( + rb.linvel() + Vec3::new(linear_x as Real, linear_y as Real, linear_z as Real), + wake_up > 0, + ); + rb.set_angvel( + rb.angvel() + Vec3::new(angular_x as Real, angular_y as Real, angular_z as Real), + wake_up > 0, + ); } - - rb.set_linvel( - rb.linvel() + Vec3::new(linear_x as Real, linear_y as Real, linear_z as Real), - wake_up > 0, - ); - rb.set_angvel( - rb.angvel() + Vec3::new(angular_x as Real, angular_y as Real, angular_z as Real), - wake_up > 0, - ); }) } @@ -1288,27 +1282,23 @@ pub extern "system" fn Java_dev_ryanhcode_sable_physics_impl_rapier_Rapier3D_app let sable_data = scene.sable_data.read().unwrap(); let mut sim_data = scene.sim_data.write().unwrap(); - let body = sable_data - .rigid_bodies - .get(&(id as LevelColliderID)) - .unwrap(); - let rb = &mut sim_data.rigid_body_set[*body]; - - if wake_up == 0 && rb.is_sleeping() { - return; - } + if let Some(rb) = get_rigid_body_mut(&mut sim_data, &sable_data, id as LevelColliderID) { + if wake_up == 0 && rb.is_sleeping() { + return; + } - let force: Vec3 = rb - .rotation() - .mul_vec3(Vec3::new(fx as Real, fy as Real, fz as Real)); - let force_pos = rb - .position() - .transform_point(Vec3::new(x as Real, y as Real, z as Real)); + let force: Vec3 = rb + .rotation() + .mul_vec3(Vec3::new(fx as Real, fy as Real, fz as Real)); + let force_pos = rb + .position() + .transform_point(Vec3::new(x as Real, y as Real, z as Real)); - rb.apply_impulse(force, wake_up > 0); + rb.apply_impulse(force, wake_up > 0); - let torque_impulse = (force_pos - rb.position().translation).cross(force); - rb.apply_torque_impulse(torque_impulse, wake_up > 0); + let torque_impulse = (force_pos - rb.position().translation).cross(force); + rb.apply_torque_impulse(torque_impulse, wake_up > 0); + } }) } @@ -1333,25 +1323,21 @@ pub extern "system" fn Java_dev_ryanhcode_sable_physics_impl_rapier_Rapier3D_app let sable_data = scene.sable_data.read().unwrap(); let mut sim_data = scene.sim_data.write().unwrap(); - let body = sable_data - .rigid_bodies - .get(&(id as LevelColliderID)) - .unwrap(); - let rb = &mut sim_data.rigid_body_set[*body]; - - if wake_up == 0 && rb.is_sleeping() { - return; - } + if let Some(rb) = get_rigid_body_mut(&mut sim_data, &sable_data, id as LevelColliderID) { + if wake_up == 0 && rb.is_sleeping() { + return; + } - let force: Vec3 = rb - .rotation() - .mul_vec3(Vec3::new(fx as Real, fy as Real, fz as Real)); - rb.apply_impulse(force, wake_up > 0); + let force: Vec3 = rb + .rotation() + .mul_vec3(Vec3::new(fx as Real, fy as Real, fz as Real)); + rb.apply_impulse(force, wake_up > 0); - let torque: Vec3 = rb - .rotation() - .mul_vec3(Vec3::new(tx as Real, ty as Real, tz as Real)); - rb.apply_torque_impulse(torque, wake_up > 0); + let torque: Vec3 = rb + .rotation() + .mul_vec3(Vec3::new(tx as Real, ty as Real, tz as Real)); + rb.apply_torque_impulse(torque, wake_up > 0); + } }) } @@ -1370,20 +1356,16 @@ pub extern "system" fn Java_dev_ryanhcode_sable_physics_impl_rapier_Rapier3D_get let sable_data = scene.sable_data.read().unwrap(); let sim_data = scene.sim_data.read().unwrap(); - let body = sable_data - .rigid_bodies - .get(&(id as LevelColliderID)) - .unwrap(); - let rb = &sim_data.rigid_body_set[*body]; + if let Some(rb) = get_rigid_body(&sim_data, &sable_data, id as LevelColliderID) { + let vel = rb.linvel(); - let vel = rb.linvel(); - - _env.set_double_array_region( - &store, - 0, - &[vel.x as jdouble, vel.y as jdouble, vel.z as jdouble], - ) - .unwrap(); + _env.set_double_array_region( + &store, + 0, + &[vel.x as jdouble, vel.y as jdouble, vel.z as jdouble], + ) + .unwrap(); + } }) } @@ -1402,19 +1384,15 @@ pub extern "system" fn Java_dev_ryanhcode_sable_physics_impl_rapier_Rapier3D_get let sable_data = scene.sable_data.read().unwrap(); let sim_data = scene.sim_data.read().unwrap(); - let body = sable_data - .rigid_bodies - .get(&(id as LevelColliderID)) - .unwrap(); - let rb = &sim_data.rigid_body_set[*body]; - - let vel = rb.angvel(); + if let Some(rb) = get_rigid_body(&sim_data, &sable_data, id as LevelColliderID) { + let vel = rb.angvel(); - _env.set_double_array_region( - &store, - 0, - &[vel.x as jdouble, vel.y as jdouble, vel.z as jdouble], - ) - .unwrap(); + _env.set_double_array_region( + &store, + 0, + &[vel.x as jdouble, vel.y as jdouble, vel.z as jdouble], + ) + .unwrap(); + } }) } From 3ba7c2578523da97d69f8e4083185a840e83dd27 Mon Sep 17 00:00:00 2001 From: steban Date: Wed, 29 Jul 2026 04:02:55 +0200 Subject: [PATCH 2/2] Guard remaining level_colliders lookups against missing keys PR #1396 (ssarg / mynameisgrass) fixed the non-unwinding panic on the rigid_bodies registry (the reported getPose crash), but a second registry, level_colliders, was left fully unguarded. On a production NeoForge 1.21.1 server this reproduced the exact same failure mode (SIGABRT from a Rust panic across an extern "system" JNI boundary) one call further down the same code path, in setCenterOfMass: thread '' panicked at rapier/src/lib.rs:557:14: called `Option::unwrap()` on a `None` value thread caused non-unwinding panic. aborting. This commit audits every JNI entry point that indexes level_colliders (or a rigid_bodies handle later resolved through it) by an id supplied from Java, and replaces unwrap()/expect()/direct indexing with Option-based guards that no-op (or fall back to an unswapped/zero default where the lookup only affects an optimization) when the body has already been removed - mirroring the pattern #1396 applied to rigid_bodies. Sites fixed, none of which were touched by #1396: - lib.rs: setCenterOfMass, setLocalBounds, addChunk - contraptions.rs: get_kinematic_collider_info (now returns Option) and its two callers; createKinematicContraption's mount lookup; removeKinematicContraption against a double-remove - rope.rs: tick() start/end attachment anchor updates, for a rope still attached to a sub-level that has since unloaded - dispatcher.rs: the collision-pair swap heuristic, and both world_vs_world contact-manifold paths - hooks.rs: fake-velocity lookups in both solver-contact hooks Where a guarded lookup only feeds a performance heuristic (the dispatcher swap order) or a purely cosmetic velocity nudge, the fallback is the pre-existing default behavior rather than skipping the tick, since skipping there is not required for correctness. Verified against the production crash: applying #1396 alone traded the original getPose panic for this setCenterOfMass panic on the same server within the same physics tick chain (onStatsChanged calls setCenterOfMass then setLocalBounds back to back). With this commit on top, both crashes are gone under the same reproduction (repeated sub-level load/unload near a player). This patch was written and applied by Claude (Anthropic) at the repository owner's direction - i.e. it is vibecoded: the owner described the crash and asked for a fix, Claude read #1396, found the gap, wrote the guards, cross-compiled sable_rapier for x86_64-unknown-linux-gnu.2.17 to match upstream's release target, and verified the fix in place on the affected server before this commit was prepared. It has not been reviewed by a Rust engineer beyond that runtime verification - please review the unwrap/expect removals accordingly, in particular whether any of the now-silent no-op paths should instead log or clean up related state (e.g. dangling rope attachments after a level_colliders entry disappears from under it). --- .../src/main/rust/rapier/src/contraptions.rs | 34 ++++----- .../src/main/rust/rapier/src/dispatcher.rs | 69 ++++++++++++------- .../src/main/rust/rapier/src/hooks.rs | 14 ++-- sable_rapier/src/main/rust/rapier/src/lib.rs | 19 +++-- sable_rapier/src/main/rust/rapier/src/rope.rs | 61 +++++++++------- 5 files changed, 117 insertions(+), 80 deletions(-) diff --git a/sable_rapier/src/main/rust/rapier/src/contraptions.rs b/sable_rapier/src/main/rust/rapier/src/contraptions.rs index 480cab37..9da410af 100644 --- a/sable_rapier/src/main/rust/rapier/src/contraptions.rs +++ b/sable_rapier/src/main/rust/rapier/src/contraptions.rs @@ -36,11 +36,8 @@ macro_rules! extract_jint_array { fn get_kinematic_collider_info( sable: &mut SableSceneData, id: jint, -) -> &mut ActiveLevelColliderInfo { - sable - .level_colliders - .get_mut(&(id as LevelColliderID)) - .expect("No kinematic contraption with given ID!") +) -> Option<&mut ActiveLevelColliderInfo> { + sable.level_colliders.get_mut(&(id as LevelColliderID)) } #[unsafe(no_mangle)] @@ -66,18 +63,17 @@ pub extern "system" fn Java_dev_ryanhcode_sable_physics_impl_rapier_Rapier3D_cre .insert(RigidBodyBuilder::kinematic_position_based()); Some(new_body) } else { - Some( - *sable_data - .rigid_bodies - .get(&(mount_id as LevelColliderID)) - .unwrap(), - ) + sable_data + .rigid_bodies + .get(&(mount_id as LevelColliderID)) + .copied() }; let mount_rigid_body: RigidBodyHandle = if let Some(body) = mount_rigid_body { body } else { - panic!("woops!") + // The mount body was unloaded before this call reached us; nothing to attach to. + return; }; let level_collider = LevelCollider::new(Some(id as LevelColliderID), false); @@ -140,7 +136,9 @@ pub extern "system" fn Java_dev_ryanhcode_sable_physics_impl_rapier_Rapier3D_set let mut sim_data = scene.sim_data.write().unwrap(); let mut sable_data = scene.sable_data.write().unwrap(); - let info = get_kinematic_collider_info(&mut sable_data, id); + let Some(info) = get_kinematic_collider_info(&mut sable_data, id) else { + return; + }; let collider_handle = info.collider; let collider = sim_data.collider_set.get_mut(collider_handle); @@ -218,7 +216,9 @@ pub extern "system" fn Java_dev_ryanhcode_sable_physics_impl_rapier_Rapier3D_add with_handle(handle, |scene| { let mut sable_data = scene.sable_data.write().unwrap(); - let info = get_kinematic_collider_info(&mut sable_data, id); + let Some(info) = get_kinematic_collider_info(&mut sable_data, id) else { + return; + }; if let Some(chunk_map) = &mut info.chunk_map { chunk_map.insert(crate::scene::pack_section_pos(x, y, z), chunk); } @@ -240,8 +240,10 @@ pub extern "system" fn Java_dev_ryanhcode_sable_physics_impl_rapier_Rapier3D_rem let sim_data = &mut *sim_data; let mut sable_data = scene.sable_data.write().unwrap(); - let info = sable_data.level_colliders.remove(&(id as LevelColliderID)); - let info = info.unwrap(); + // Already removed (e.g. a duplicate/late removal after an unload) - nothing to do. + let Some(info) = sable_data.level_colliders.remove(&(id as LevelColliderID)) else { + return; + }; sim_data.collider_set.remove( info.collider, diff --git a/sable_rapier/src/main/rust/rapier/src/dispatcher.rs b/sable_rapier/src/main/rust/rapier/src/dispatcher.rs index 8fdbe03a..428756ff 100644 --- a/sable_rapier/src/main/rust/rapier/src/dispatcher.rs +++ b/sable_rapier/src/main/rust/rapier/src/dispatcher.rs @@ -184,25 +184,35 @@ where let sable_data = self.sable_data.read().unwrap(); let body_1 = g1 .id - .map(|id| &sable_data.level_colliders[&(id as LevelColliderID)]) - .unwrap(); + .and_then(|id| sable_data.level_colliders.get(&(id as LevelColliderID))); let body_2 = g2 .id - .map(|id| &sable_data.level_colliders[&(id as LevelColliderID)]) - .unwrap(); - - let extents_1 = body_1.local_bounds_max.unwrap() - - body_1.local_bounds_min.unwrap() - + IVec3::ONE; - let extents_2 = body_2.local_bounds_max.unwrap() - - body_2.local_bounds_min.unwrap() - + IVec3::ONE; - - let volume_1 = extents_1.x * extents_1.y * extents_1.z; - let volume_2 = extents_2.x * extents_2.y * extents_2.z; - - // Swap the bodies so we're always doing the least amount of work possible for collision detection - volume_1 < volume_2 + .and_then(|id| sable_data.level_colliders.get(&(id as LevelColliderID))); + + // A body can be unloaded mid-step, or its bounds may not be set yet. + // Swapping is only a performance choice, so fall back to the unswapped + // order rather than aborting the process. + match (body_1, body_2) { + (Some(body_1), Some(body_2)) => match ( + body_1.local_bounds_max, + body_1.local_bounds_min, + body_2.local_bounds_max, + body_2.local_bounds_min, + ) { + (Some(max_1), Some(min_1), Some(max_2), Some(min_2)) => { + let extents_1 = max_1 - min_1 + IVec3::ONE; + let extents_2 = max_2 - min_2 + IVec3::ONE; + + let volume_1 = extents_1.x * extents_1.y * extents_1.z; + let volume_2 = extents_2.x * extents_2.y * extents_2.z; + + // Swap the bodies so we're always doing the least amount of work possible for collision detection + volume_1 < volume_2 + } + _ => false, + }, + _ => false, + } }; if swap { @@ -258,8 +268,10 @@ impl SableDispatcher { let collider_info = g1 .id - .map(|id| &sable_data.level_colliders[&(id as LevelColliderID)]); - let center_of_mass_1 = collider_info.map_or(DVec3::ZERO, |b| b.center_of_mass.unwrap()); + .and_then(|id| sable_data.level_colliders.get(&(id as LevelColliderID))); + let center_of_mass_1 = collider_info + .and_then(|b| b.center_of_mass) + .unwrap_or(DVec3::ZERO); let mut local_aabb = g2.compute_aabb(pos12); @@ -443,10 +455,21 @@ impl SableDispatcher { let collider_info_1 = g1 .id - .map(|id| &sable_data.level_colliders[&(id as LevelColliderID)]); - let collider_info_2 = &sable_data.level_colliders[&(g2.id.unwrap() as LevelColliderID)]; - let center_of_mass_1 = collider_info_1.map_or(DVec3::ZERO, |b| b.center_of_mass.unwrap()); - let center_of_mass_2 = collider_info_2.center_of_mass.unwrap(); + .and_then(|id| sable_data.level_colliders.get(&(id as LevelColliderID))); + // g2 is required below; if it was unloaded mid-step, skip contact generation for + // this pair instead of aborting the process. + let Some(collider_info_2) = g2 + .id + .and_then(|id| sable_data.level_colliders.get(&(id as LevelColliderID))) + else { + return; + }; + let center_of_mass_1 = collider_info_1 + .and_then(|b| b.center_of_mass) + .unwrap_or(DVec3::ZERO); + let Some(center_of_mass_2) = collider_info_2.center_of_mass else { + return; + }; let chunk_access_1: &dyn ChunkAccess = if let Some(info) = collider_info_1 && info.has_own_chunks() diff --git a/sable_rapier/src/main/rust/rapier/src/hooks.rs b/sable_rapier/src/main/rust/rapier/src/hooks.rs index 47cdd957..05b4ee9e 100644 --- a/sable_rapier/src/main/rust/rapier/src/hooks.rs +++ b/sable_rapier/src/main/rust/rapier/src/hooks.rs @@ -128,12 +128,15 @@ impl SablePhysicsHooks { level_collider_a: Option<&LevelCollider>, ) -> Vec3 { if let Some(level_collider_a) = level_collider_a - && level_collider_a.id.is_some() + && let Some(id) = level_collider_a.id { let sable_data = self.sable_data.read().unwrap(); - let collider_info = - &sable_data.level_colliders[&(level_collider_a.id.unwrap() as LevelColliderID)]; + // The body may have been unloaded mid-step; treat it as having no fake velocity. + let Some(collider_info) = sable_data.level_colliders.get(&(id as LevelColliderID)) + else { + return Vec3::ZERO; + }; if let Some(fake_velo) = collider_info.fake_velocities { let transform = collider_a.position(); @@ -160,8 +163,9 @@ impl SablePhysicsHooks { let (tangent_velo, center_of_mass, skip_contact_events) = { let sable_data = self.sable_data.read().unwrap(); - let collider_info = - level_collider.and_then(|lc| lc.id.map(|id| &sable_data.level_colliders[&(id)])); + let collider_info = level_collider + .and_then(|lc| lc.id) + .and_then(|id| sable_data.level_colliders.get(&(id))); let mut tangent_velo = Vec3::ZERO; if let Some(fake_velo) = collider_info.and_then(|info| info.fake_velocities) { diff --git a/sable_rapier/src/main/rust/rapier/src/lib.rs b/sable_rapier/src/main/rust/rapier/src/lib.rs index bca60aef..21729adc 100644 --- a/sable_rapier/src/main/rust/rapier/src/lib.rs +++ b/sable_rapier/src/main/rust/rapier/src/lib.rs @@ -551,10 +551,9 @@ pub extern "system" fn Java_dev_ryanhcode_sable_physics_impl_rapier_Rapier3D_set ) { with_handle(handle, |scene| { let mut sable_data = scene.sable_data.write().unwrap(); - let info = sable_data - .level_colliders - .get_mut(&(id as LevelColliderID)) - .unwrap(); + let Some(info) = sable_data.level_colliders.get_mut(&(id as LevelColliderID)) else { + return; + }; info.center_of_mass = Some(DVec3::new(x, y, z)); let mut sim_data = scene.sim_data.write().unwrap(); update_collider_aabb(&mut sim_data, info); @@ -586,7 +585,9 @@ pub extern "system" fn Java_dev_ryanhcode_sable_physics_impl_rapier_Rapier3D_set .. } = &mut *sable_data; - let info = level_colliders.get_mut(&(id as LevelColliderID)).unwrap(); + let Some(info) = level_colliders.get_mut(&(id as LevelColliderID)) else { + return; + }; info.set_local_bounds( IVec3::new(min_x, min_y, min_z), IVec3::new(max_x, max_y, max_z), @@ -796,11 +797,9 @@ pub extern "system" fn Java_dev_ryanhcode_sable_physics_impl_rapier_Rapier3D_add let chunk = main_level_chunks.get(&pack_section_pos(x, y, z)).unwrap(); if global == 0 { if object_id != -1 { - let body = level_colliders - .get_mut(&(object_id as LevelColliderID)) - .unwrap(); - - body.insert_chunk(chunk, x, y, z, collider_map); + if let Some(body) = level_colliders.get_mut(&(object_id as LevelColliderID)) { + body.insert_chunk(chunk, x, y, z, collider_map); + } } } else { for bx in 0..16 { diff --git a/sable_rapier/src/main/rust/rapier/src/rope.rs b/sable_rapier/src/main/rust/rapier/src/rope.rs index 84a31010..8fd7cac1 100644 --- a/sable_rapier/src/main/rust/rapier/src/rope.rs +++ b/sable_rapier/src/main/rust/rapier/src/rope.rs @@ -54,19 +54,24 @@ pub fn tick(scene: &PhysicsScene) { if !sim.impulse_joint_set.contains(attachment.joint) { dead_start_attachments.push(id.clone()); } else { - let local_anchor = attachment.location - - if let Some(id_b) = attachment.sub_level_id { - let rb_b = &sable_data.level_colliders[&id_b]; - rb_b.center_of_mass.unwrap() - } else { - DVec3::ZERO - }; - - let impulse_joint = sim - .impulse_joint_set - .get_mut(attachment.joint, false) - .unwrap(); - impulse_joint.data.set_local_anchor1(local_anchor.as_vec3()); + // The attached sub-level may have been unloaded while the rope is still + // alive; in that case leave the anchor untouched instead of aborting. + let offset = match attachment.sub_level_id { + Some(id_b) => sable_data + .level_colliders + .get(&id_b) + .and_then(|rb_b| rb_b.center_of_mass), + None => Some(DVec3::ZERO), + }; + + if let Some(offset) = offset { + let local_anchor = attachment.location - offset; + if let Some(impulse_joint) = + sim.impulse_joint_set.get_mut(attachment.joint, false) + { + impulse_joint.data.set_local_anchor1(local_anchor.as_vec3()); + } + } } } @@ -74,19 +79,23 @@ pub fn tick(scene: &PhysicsScene) { if !sim.impulse_joint_set.contains(attachment.joint) { dead_end_attachments.push(id.clone()); } else { - let local_anchor = attachment.location - - if let Some(id_b) = attachment.sub_level_id { - let rb_b = &sable_data.level_colliders[&id_b]; - rb_b.center_of_mass.unwrap() - } else { - DVec3::ZERO - }; - - let impulse_joint = sim - .impulse_joint_set - .get_mut(attachment.joint, false) - .unwrap(); - impulse_joint.data.set_local_anchor1(local_anchor.as_vec3()); + // Same guard as the start attachment above. + let offset = match attachment.sub_level_id { + Some(id_b) => sable_data + .level_colliders + .get(&id_b) + .and_then(|rb_b| rb_b.center_of_mass), + None => Some(DVec3::ZERO), + }; + + if let Some(offset) = offset { + let local_anchor = attachment.location - offset; + if let Some(impulse_joint) = + sim.impulse_joint_set.get_mut(attachment.joint, false) + { + impulse_joint.data.set_local_anchor1(local_anchor.as_vec3()); + } + } } } }