From 27a3c14502f5a201acb93705fc72fc057478c5bd Mon Sep 17 00:00:00 2001 From: sasch Date: Fri, 15 May 2026 21:03:16 +0200 Subject: [PATCH 1/6] add getting forces --- README.md | 4 +- .../github/techtastic/cc_sable/CCSable.java | 4 + .../techtastic/cc_sable/apis/SubLevelAPI.java | 14 +-- .../techtastic/cc_sable/util/Physicker.java | 104 ++++++++++++++++++ .../computercraft/lua/rom/apis/sublevel.lua | 6 + 5 files changed, 124 insertions(+), 8 deletions(-) create mode 100644 common/src/main/java/io/github/techtastic/cc_sable/util/Physicker.java diff --git a/README.md b/README.md index 30fd1b7..f9367b6 100644 --- a/README.md +++ b/README.md @@ -34,4 +34,6 @@ These new details will be listed under the `"sable"` key and have the following - `transitionSpeed: number` - `preventSelfLift: boolean` - `scaleWithGravity: boolean` - - `scaleWithPressure: boolean` \ No newline at end of file + - `scaleWithPressure: boolean` + + \ No newline at end of file diff --git a/common/src/main/java/io/github/techtastic/cc_sable/CCSable.java b/common/src/main/java/io/github/techtastic/cc_sable/CCSable.java index 4a38ec6..045dc57 100644 --- a/common/src/main/java/io/github/techtastic/cc_sable/CCSable.java +++ b/common/src/main/java/io/github/techtastic/cc_sable/CCSable.java @@ -4,8 +4,10 @@ import dan200.computercraft.api.detail.VanillaDetailRegistries; import dev.ryanhcode.sable.physics.config.block_properties.PhysicsBlockPropertyHelper; import dev.ryanhcode.sable.physics.floating_block.FloatingBlockMaterial; +import dev.ryanhcode.sable.platform.SableEventPlatform; import io.github.techtastic.cc_sable.apis.AerodynamicsAPI; import io.github.techtastic.cc_sable.apis.SubLevelAPI; +import io.github.techtastic.cc_sable.util.Physicker; import java.util.Map; @@ -16,6 +18,8 @@ public static void init() { ComputerCraftAPI.registerAPIFactory(SubLevelAPI::new); ComputerCraftAPI.registerAPIFactory(AerodynamicsAPI::new); + SableEventPlatform.INSTANCE.onPostPhysicsTick(Physicker::onPostPhysicsTick); + VanillaDetailRegistries.BLOCK_IN_WORLD.addProvider((data, object) -> data.put("sable", Map.of( "mass", PhysicsBlockPropertyHelper.getMass(object.level(), object.pos(), object.state()), "friction", PhysicsBlockPropertyHelper.getFriction(object.state()), diff --git a/common/src/main/java/io/github/techtastic/cc_sable/apis/SubLevelAPI.java b/common/src/main/java/io/github/techtastic/cc_sable/apis/SubLevelAPI.java index 6cbf206..590f6b7 100644 --- a/common/src/main/java/io/github/techtastic/cc_sable/apis/SubLevelAPI.java +++ b/common/src/main/java/io/github/techtastic/cc_sable/apis/SubLevelAPI.java @@ -5,16 +5,11 @@ import dan200.computercraft.api.lua.LuaException; import dan200.computercraft.api.lua.LuaFunction; import dev.ryanhcode.sable.Sable; -import dev.ryanhcode.sable.api.SubLevelHelper; -import dev.ryanhcode.sable.api.sublevel.SubLevelContainer; import dev.ryanhcode.sable.companion.SableCompanion; -import dev.ryanhcode.sable.companion.SubLevelAccess; -import dev.ryanhcode.sable.physics.config.dimension_physics.DimensionPhysicsData; import dev.ryanhcode.sable.sublevel.ServerSubLevel; import dev.ryanhcode.sable.sublevel.SubLevel; import io.github.techtastic.cc_sable.util.CCSableUtils; -import net.minecraft.server.level.ServerLevel; -import net.minecraft.world.level.Level; +import io.github.techtastic.cc_sable.util.Physicker; import org.joml.Vector3d; import org.joml.Vector3dc; import org.jspecify.annotations.NonNull; @@ -36,7 +31,7 @@ private ServerSubLevel getSublevel() throws LuaException { @Override public final String @NonNull [] getNames() { - return new String[] {"sublevel"}; + return new String[]{"sublevel"}; } @LuaFunction(mainThread = true) @@ -111,4 +106,9 @@ public final Map> getInertiaTensor() throws LuaExcep public final Map> getInverseInertiaTensor() throws LuaException { return CCSableUtils.toLua(getSublevel().getMassTracker().getInverseInertiaTensor()); } + + @LuaFunction + public final Object getForces() throws LuaException { + return Physicker.requestForces(system, getSublevel()); + } } diff --git a/common/src/main/java/io/github/techtastic/cc_sable/util/Physicker.java b/common/src/main/java/io/github/techtastic/cc_sable/util/Physicker.java new file mode 100644 index 0000000..25ca424 --- /dev/null +++ b/common/src/main/java/io/github/techtastic/cc_sable/util/Physicker.java @@ -0,0 +1,104 @@ +package io.github.techtastic.cc_sable.util; + +import dan200.computercraft.api.lua.IComputerSystem; +import dev.ryanhcode.sable.Sable; +import dev.ryanhcode.sable.api.physics.force.ForceGroup; +import dev.ryanhcode.sable.api.physics.force.ForceGroups; +import dev.ryanhcode.sable.api.physics.force.QueuedForceGroup; +import dev.ryanhcode.sable.companion.math.Pose3d; +import dev.ryanhcode.sable.physics.config.dimension_physics.DimensionPhysicsData; +import dev.ryanhcode.sable.sublevel.ServerSubLevel; +import dev.ryanhcode.sable.sublevel.system.SubLevelPhysicsSystem; +import net.minecraft.resources.ResourceLocation; +import org.joml.Vector3d; +import org.joml.Vector3dc; +import org.jspecify.annotations.Nullable; + +import java.util.*; + +/** + * Track "force requests" for sublevels because Sable be like "waaah no you cant request forces mid-tick" + */ +public class Physicker { + private static Map LOCKS = new HashMap(); + + public static void onPostPhysicsTick(SubLevelPhysicsSystem activeSystem, double v) { + Object lock = getLock(activeSystem); + synchronized (lock) { + lock.notifyAll(); + } + } + + private static synchronized Object getLock(SubLevelPhysicsSystem activeSystem) { + ResourceLocation key = activeSystem.getLevel().dimension().location(); + Object lock = LOCKS.get(key); + if(lock == null) lock = new Object(); + LOCKS.put(key, lock); + return lock; + } + + + public static Object requestForces(IComputerSystem system, ServerSubLevel sublevel) { + sublevel.enableIndividualQueuedForcesTracking(true); + try { + Object lock = getLock(SubLevelPhysicsSystem.get(sublevel.getLevel())); + synchronized (lock) { + lock.wait(100); + } + return createValueMap(sublevel); + } catch (InterruptedException e) { + return null; + } + } + + private static Map>> createValueMap(ServerSubLevel level) { + Map>> returnValue = new HashMap<>(); + Map forceGroups = level.getQueuedForceGroups(); + + { + final Vector3dc centerOfMass = level.getMassTracker().getCenterOfMass(); + final Pose3d pose = level.logicalPose(); + final Vector3d localGravity = pose.transformNormalInverse(DimensionPhysicsData.getGravity(level.getLevel())).mul(level.getMassTracker().getMass()); + + Map> force = new HashMap<>(); + Map gravityMap = new HashMap<>(); + gravityMap.put("position", CCSableUtils.toLua(centerOfMass)); + gravityMap.put("force", CCSableUtils.toLua(localGravity)); + + force.put(1, gravityMap); + returnValue.put(Sable.sablePath("gravity").toString() /* thanks veil (?) for making life difficult @todo: hardcoded because veil is being annoying */, force); + } + + if (forceGroups == null || forceGroups.isEmpty()) + return returnValue; + + SubLevelPhysicsSystem physicsSystem = SubLevelPhysicsSystem.get(level.getLevel()); + final double timeStep = 1.0 / 20.0 / physicsSystem.getConfig().substepsPerTick; // see DiagramEntity of aeronautics + + for (Map.Entry set : forceGroups.entrySet()) { + Map> force = new HashMap<>(); + + List pointForces = set.getValue().getRecordedPointForces(); + if (pointForces.isEmpty()) + continue; + + + int index = 1; // not a big fan of for loops + for (QueuedForceGroup.PointForce pointForce : pointForces) { + + Map pointForceLua = new HashMap<>(); + + pointForceLua.put("position", CCSableUtils.toLua(pointForce.point())); + pointForceLua.put("force", CCSableUtils.toLua(new Vector3d(pointForce.force()).div(timeStep))); + + force.put(index, pointForceLua); + index++; + } + + ResourceLocation key = ForceGroups.REGISTRY.getKey(set.getKey()); + returnValue.put(key.toString(), force); + } + + return returnValue; + } +} diff --git a/common/src/main/resources/data/computercraft/lua/rom/apis/sublevel.lua b/common/src/main/resources/data/computercraft/lua/rom/apis/sublevel.lua index d950f53..7ebaa5d 100644 --- a/common/src/main/resources/data/computercraft/lua/rom/apis/sublevel.lua +++ b/common/src/main/resources/data/computercraft/lua/rom/apis/sublevel.lua @@ -75,6 +75,12 @@ -- @treturn matrix the inverse inertia tensor of the Sub-Level -- @raise This method errors if there is no Sub-Level associated with the computer. +--- Gets all forces acting upon the sublevel. +-- @function getForces +-- @treturn table All forces in the format { TYPE = { [1] = {position, force}, ... }, ... } +-- @raise This method errors if there is no Sub-Level associated with the computer. + + if not sublevel then error("Cannot load Sub-Level API on computer") end From 1751cffbc1eb527c47e5f28defb889a10009a09c Mon Sep 17 00:00:00 2001 From: sasch Date: Sat, 16 May 2026 14:56:02 +0200 Subject: [PATCH 2/6] fix: convert position and force to actual vectors oops https://github.com/TechTastic/CC-Sable/pull/9#discussion_r3252807674 --- .../data/computercraft/lua/rom/apis/sublevel.lua | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/common/src/main/resources/data/computercraft/lua/rom/apis/sublevel.lua b/common/src/main/resources/data/computercraft/lua/rom/apis/sublevel.lua index 7ebaa5d..dcfc3f5 100644 --- a/common/src/main/resources/data/computercraft/lua/rom/apis/sublevel.lua +++ b/common/src/main/resources/data/computercraft/lua/rom/apis/sublevel.lua @@ -117,6 +117,22 @@ for k,v in pairs(native) do end return matrix.from2DArray(result) end + elseif k == "getForces" then + env[k] = function() + local result, err = v() + if err then + error(err) + end + + for name, force in pairs(result) do + for i, point in ipairs(force) do + point.position = vector.new(point.position.x, point.position.y, point.position.z) + point.force = vector.new(point.force.x, point.force.y, point.force.z) + end + end + + return result + end else env[k] = v end From 36acde194b9d5e666c58cf6f99539d054fa11262 Mon Sep 17 00:00:00 2001 From: sasch Date: Sat, 16 May 2026 15:10:07 +0200 Subject: [PATCH 3/6] fix: leverage Javas stdlib concurrency features absolutely evil https://github.com/TechTastic/CC-Sable/pull/9#discussion_r3252805244 --- .../techtastic/cc_sable/apis/SubLevelAPI.java | 9 +++- .../techtastic/cc_sable/util/Physicker.java | 41 +++++++++---------- 2 files changed, 27 insertions(+), 23 deletions(-) diff --git a/common/src/main/java/io/github/techtastic/cc_sable/apis/SubLevelAPI.java b/common/src/main/java/io/github/techtastic/cc_sable/apis/SubLevelAPI.java index 590f6b7..fceede7 100644 --- a/common/src/main/java/io/github/techtastic/cc_sable/apis/SubLevelAPI.java +++ b/common/src/main/java/io/github/techtastic/cc_sable/apis/SubLevelAPI.java @@ -109,6 +109,13 @@ public final Map> getInverseInertiaTensor() throws L @LuaFunction public final Object getForces() throws LuaException { - return Physicker.requestForces(system, getSublevel()); + try { + Object o = Physicker.requestForces(system, getSublevel()); + if (o == null) + throw new LuaException("timeout"); + return o; + } catch (InterruptedException e) { + throw new LuaException("interrupted"); + } } } diff --git a/common/src/main/java/io/github/techtastic/cc_sable/util/Physicker.java b/common/src/main/java/io/github/techtastic/cc_sable/util/Physicker.java index 25ca424..4a49a53 100644 --- a/common/src/main/java/io/github/techtastic/cc_sable/util/Physicker.java +++ b/common/src/main/java/io/github/techtastic/cc_sable/util/Physicker.java @@ -8,6 +8,7 @@ import dev.ryanhcode.sable.companion.math.Pose3d; import dev.ryanhcode.sable.physics.config.dimension_physics.DimensionPhysicsData; import dev.ryanhcode.sable.sublevel.ServerSubLevel; +import dev.ryanhcode.sable.sublevel.SubLevel; import dev.ryanhcode.sable.sublevel.system.SubLevelPhysicsSystem; import net.minecraft.resources.ResourceLocation; import org.joml.Vector3d; @@ -15,42 +16,38 @@ import org.jspecify.annotations.Nullable; import java.util.*; +import java.util.concurrent.*; /** * Track "force requests" for sublevels because Sable be like "waaah no you cant request forces mid-tick" */ public class Physicker { - private static Map LOCKS = new HashMap(); + private static ConcurrentMap REQUESTS = new ConcurrentHashMap<>(); public static void onPostPhysicsTick(SubLevelPhysicsSystem activeSystem, double v) { - Object lock = getLock(activeSystem); - synchronized (lock) { - lock.notifyAll(); + for (Map.Entry entry : REQUESTS.entrySet()) { + REQUESTS.remove(entry.getKey()); + try { + entry.getValue().receiver.put(createValueMap(entry.getValue().subLevel)); + } catch (InterruptedException e) { + e.printStackTrace(); + } } } - private static synchronized Object getLock(SubLevelPhysicsSystem activeSystem) { - ResourceLocation key = activeSystem.getLevel().dimension().location(); - Object lock = LOCKS.get(key); - if(lock == null) lock = new Object(); - LOCKS.put(key, lock); - return lock; - } - - public static Object requestForces(IComputerSystem system, ServerSubLevel sublevel) { + public static Object requestForces(IComputerSystem system, ServerSubLevel sublevel) throws InterruptedException { + int id = system.getID(); + BlockingQueue receiver = new ArrayBlockingQueue<>(1); + REQUESTS.put(id, new Request(sublevel, receiver)); sublevel.enableIndividualQueuedForcesTracking(true); - try { - Object lock = getLock(SubLevelPhysicsSystem.get(sublevel.getLevel())); - synchronized (lock) { - lock.wait(100); - } - return createValueMap(sublevel); - } catch (InterruptedException e) { - return null; - } + Object retVal = receiver.poll(100, TimeUnit.MILLISECONDS); + sublevel.enableIndividualQueuedForcesTracking(false); + return retVal; } + private record Request(ServerSubLevel subLevel, BlockingQueue receiver) { } + private static Map>> createValueMap(ServerSubLevel level) { Map>> returnValue = new HashMap<>(); Map forceGroups = level.getQueuedForceGroups(); From 8c8f0b432deae227ad2bcfd368bc35628704b4bf Mon Sep 17 00:00:00 2001 From: sasch Date: Sat, 16 May 2026 20:52:07 +0200 Subject: [PATCH 4/6] fix: attempt to fix erratic nil --- .../java/io/github/techtastic/cc_sable/util/Physicker.java | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/common/src/main/java/io/github/techtastic/cc_sable/util/Physicker.java b/common/src/main/java/io/github/techtastic/cc_sable/util/Physicker.java index 4a49a53..c48d733 100644 --- a/common/src/main/java/io/github/techtastic/cc_sable/util/Physicker.java +++ b/common/src/main/java/io/github/techtastic/cc_sable/util/Physicker.java @@ -26,6 +26,9 @@ public class Physicker { public static void onPostPhysicsTick(SubLevelPhysicsSystem activeSystem, double v) { for (Map.Entry entry : REQUESTS.entrySet()) { + if(!entry.getValue().subLevel().getLevel().equals(activeSystem.getLevel())) + continue; + REQUESTS.remove(entry.getKey()); try { entry.getValue().receiver.put(createValueMap(entry.getValue().subLevel)); @@ -46,7 +49,7 @@ public static Object requestForces(IComputerSystem system, ServerSubLevel sublev return retVal; } - private record Request(ServerSubLevel subLevel, BlockingQueue receiver) { } + private record Request(ServerSubLevel subLevel, BlockingQueue receiver { } private static Map>> createValueMap(ServerSubLevel level) { Map>> returnValue = new HashMap<>(); From 41e94799354426f4e6ec103ce98a092fe770cd5b Mon Sep 17 00:00:00 2001 From: sasch Date: Sat, 16 May 2026 20:59:56 +0200 Subject: [PATCH 5/6] I FORGOT A ) --- .../main/java/io/github/techtastic/cc_sable/util/Physicker.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/common/src/main/java/io/github/techtastic/cc_sable/util/Physicker.java b/common/src/main/java/io/github/techtastic/cc_sable/util/Physicker.java index c48d733..c8af8b2 100644 --- a/common/src/main/java/io/github/techtastic/cc_sable/util/Physicker.java +++ b/common/src/main/java/io/github/techtastic/cc_sable/util/Physicker.java @@ -49,7 +49,7 @@ public static Object requestForces(IComputerSystem system, ServerSubLevel sublev return retVal; } - private record Request(ServerSubLevel subLevel, BlockingQueue receiver { } + private record Request(ServerSubLevel subLevel, BlockingQueue receiver) { } private static Map>> createValueMap(ServerSubLevel level) { Map>> returnValue = new HashMap<>(); From e9a68f292a3ae909d4ca0e177b8a7e4598301429 Mon Sep 17 00:00:00 2001 From: sasch Date: Sun, 17 May 2026 01:13:32 +0200 Subject: [PATCH 6/6] increase consistency slightly --- .../src/main/java/io/github/techtastic/cc_sable/CCSable.java | 2 +- .../java/io/github/techtastic/cc_sable/util/Physicker.java | 3 +-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/common/src/main/java/io/github/techtastic/cc_sable/CCSable.java b/common/src/main/java/io/github/techtastic/cc_sable/CCSable.java index 045dc57..3c050ce 100644 --- a/common/src/main/java/io/github/techtastic/cc_sable/CCSable.java +++ b/common/src/main/java/io/github/techtastic/cc_sable/CCSable.java @@ -18,7 +18,7 @@ public static void init() { ComputerCraftAPI.registerAPIFactory(SubLevelAPI::new); ComputerCraftAPI.registerAPIFactory(AerodynamicsAPI::new); - SableEventPlatform.INSTANCE.onPostPhysicsTick(Physicker::onPostPhysicsTick); + SableEventPlatform.INSTANCE.onPhysicsTick(Physicker::onPhysicsTick); VanillaDetailRegistries.BLOCK_IN_WORLD.addProvider((data, object) -> data.put("sable", Map.of( "mass", PhysicsBlockPropertyHelper.getMass(object.level(), object.pos(), object.state()), diff --git a/common/src/main/java/io/github/techtastic/cc_sable/util/Physicker.java b/common/src/main/java/io/github/techtastic/cc_sable/util/Physicker.java index c8af8b2..f6e0d61 100644 --- a/common/src/main/java/io/github/techtastic/cc_sable/util/Physicker.java +++ b/common/src/main/java/io/github/techtastic/cc_sable/util/Physicker.java @@ -24,11 +24,10 @@ public class Physicker { private static ConcurrentMap REQUESTS = new ConcurrentHashMap<>(); - public static void onPostPhysicsTick(SubLevelPhysicsSystem activeSystem, double v) { + public static void onPhysicsTick(SubLevelPhysicsSystem activeSystem, double v) { for (Map.Entry entry : REQUESTS.entrySet()) { if(!entry.getValue().subLevel().getLevel().equals(activeSystem.getLevel())) continue; - REQUESTS.remove(entry.getKey()); try { entry.getValue().receiver.put(createValueMap(entry.getValue().subLevel));